public InputStream open() throws IOException, InterruptedException {
        if (recyclableInflaterInstance != null) {
            throw new IOException("Zip entry already open");
        }
        if (isDeflated) {
            recyclableInflaterInstance = nestedJarHandler.inflaterRecycler.acquire();
        }
        return new InputStream() {
            /** The data start offset within the physical zip file. */
            private final long dataStartOffsetWithinPhysicalZipFile = getEntryDataStartOffsetWithinPhysicalZipFile();

            /** A scratch buffer. */
            private final byte[] scratch = new byte[8192];

            /** The current 2GB chunk of the zip entry. */
            private ByteBuffer currChunkByteBuf;

            /** True if the current 2GB chunk is the last chunk in the zip entry. */
            private boolean isLastChunk;

            /** The index of the current 2GB chunk. */
            private int currChunkIdx;

            /** True if the end of the zip entry has been reached. */
            private boolean eof;

            /** The {@link Inflater} instance, or null if the entry is stored rather than deflated. */
            private final Inflater inflater = isDeflated ? recyclableInflaterInstance.getInflater() : null;

            /** True if this {@link InputStream} has been closed. */
            private final AtomicBoolean closed = new AtomicBoolean(false);

            /** The size of the {@link Inflate} buffer to use. */
            private static final int INFLATE_BUF_SIZE = 1024;

            // Open the first 2GB chunk.
            {
                // Calculate the chunk index for the first chunk
                currChunkIdx = (int) (dataStartOffsetWithinPhysicalZipFile / FileUtils.MAX_BUFFER_SIZE);

                // Get the MappedByteBuffer for the 2GB chunk, and duplicate it
                currChunkByteBuf = parentLogicalZipFile.physicalZipFile.getByteBuffer(currChunkIdx).duplicate();

                // Calculate the start position within the first chunk, and set the position of the slice.
                // N.B. the cast to Buffer is necessary, see:
                // https://github.com/plasma-umass/doppio/issues/497#issuecomment-334740243
                // https://github.com/classgraph/classgraph/issues/284#issuecomment-443612800
                final int chunkPos = (int) (dataStartOffsetWithinPhysicalZipFile
                        - (((long) currChunkIdx) * (long) FileUtils.MAX_BUFFER_SIZE));
                ((Buffer) currChunkByteBuf).position(chunkPos);

                // Calculate end pos for the first chunk, and truncate it if it overflows 2GB
                final long endPos = chunkPos + compressedSize;
                ((Buffer) currChunkByteBuf).limit((int) Math.min(FileUtils.MAX_BUFFER_SIZE, endPos));
                isLastChunk = endPos <= FileUtils.MAX_BUFFER_SIZE;
            }

            /** Advance to the next 2GB chunk. */
            private boolean readNextChunk() throws IOException, InterruptedException {
                currChunkIdx++;
                if (currChunkIdx >= parentLogicalZipFile.physicalZipFile.numMappedByteBuffers) {
                    // Ran out of chunks
                    return false;
                }

                // Calculate how many bytes were consumed in previous chunks
                final long chunkStartOff = ((long) currChunkIdx) * (long) FileUtils.MAX_BUFFER_SIZE;
                final long priorBytes = chunkStartOff - dataStartOffsetWithinPhysicalZipFile;
                final long remainingBytes = compressedSize - priorBytes;
                if (remainingBytes <= 0) {
                    return false;
                }

                // Get the MappedByteBuffer for the next 2GB chunk, and duplicate it
                currChunkByteBuf = parentLogicalZipFile.physicalZipFile.getByteBuffer(currChunkIdx).duplicate();

                // The start position for 2nd and subsequent chunks is 0.
                // N.B. the cast to Buffer is necessary, see:
                // https://github.com/plasma-umass/doppio/issues/497#issuecomment-334740243
                // https://github.com/classgraph/classgraph/issues/284#issuecomment-443612800
                ((Buffer) currChunkByteBuf).position(0);

                // Calculate end pos for the next chunk, and truncate it if it overflows 2GB
                ((Buffer) currChunkByteBuf).limit((int) Math.min(FileUtils.MAX_BUFFER_SIZE, remainingBytes));
                isLastChunk = remainingBytes <= FileUtils.MAX_BUFFER_SIZE;
                return true;
            }

            /**
             * Inflate deflated data.
             *
             * @param buf
             *            the buffer to inflate into.
             * @param off
             *            the offset within buf to start writing.
             * @param len
             *            the number of bytes of uncompressed data to read.
             * @return the number of bytes read.
             * @throws IOException
             *             if an I/O exception occurred.
             * @throws InterruptedException
             *             if the thread was interrupted.
             */
            private int readDeflated(final byte[] buf, final int off, final int len)
                    throws IOException, InterruptedException {
                try {
                    final byte[] inflateBuf = new byte[INFLATE_BUF_SIZE];
                    int numInflatedBytes;
                    while ((numInflatedBytes = inflater.inflate(buf, off, len)) == 0) {
                        if (inflater.finished() || inflater.needsDictionary()) {
                            eof = true;
                            return -1;
                        }
                        if (inflater.needsInput()) {
                            // Check if there's still data left in the current chunk
                            if (!currChunkByteBuf.hasRemaining()
                                    // No more bytes in current chunk -- get next chunk, and then make sure
                                    // that currChunkByteBuf.hasRemaining() subsequently returns true
                                    && !(readNextChunk() && currChunkByteBuf.hasRemaining())) {
                                // Ran out of data in the current chunk, and could not read a new chunk
                                throw new IOException("Unexpected EOF in deflated data");
                            }
                            // Set inflater input for the current chunk

                            // In JDK11+: simply use the following instead of all the lines below:
                            //     inflater.setInput(currChunkByteBuf);
                            // N.B. the ByteBuffer version of setInput doesn't seem to need the extra
                            // padding byte at the end when using the "nowrap" Inflater option.

                            // Copy from the ByteBuffer into a temporary byte[] array (needed for JDK<11). 
                            try {
                                final int remaining = currChunkByteBuf.remaining();
                                if (isLastChunk && remaining < inflateBuf.length) {
                                    // An extra dummy byte is needed at the end of the input stream when
                                    // using the "nowrap" Inflater option.
                                    // See: ZipFile.ZipFileInputStream.fill()
                                    currChunkByteBuf.get(inflateBuf, 0, remaining);
                                    inflateBuf[remaining] = (byte) 0;
                                    inflater.setInput(inflateBuf, 0, remaining + 1);
                                } else if (isLastChunk && remaining == inflateBuf.length) {
                                    // If this is the last chunk to read, and the number of remaining
                                    // bytes is exactly the size of the buffer, read one byte fewer than
                                    // the number of remaining bytes, to cause the last byte to be read
                                    // in an extra pass.
                                    currChunkByteBuf.get(inflateBuf, 0, remaining - 1);
                                    inflater.setInput(inflateBuf, 0, remaining - 1);
                                } else {
                                    // There are more than inflateBuf.length bytes remaining to be read,
                                    // or this is not the last chunk (i.e. read all remaining bytes in
                                    // this chunk, which will trigger the next chunk to be read on the
                                    // next loop iteration)
                                    final int bytesToRead = Math.min(inflateBuf.length, remaining);
                                    currChunkByteBuf.get(inflateBuf, 0, bytesToRead);
                                    inflater.setInput(inflateBuf, 0, bytesToRead);
                                }
                            } catch (final BufferUnderflowException e) {
                                // Should not happen
                                throw new IOException("Unexpected EOF in deflated data");
                            }
                        }
                    }
                    return numInflatedBytes;
                } catch (final DataFormatException e) {
                    throw new ZipException(
                            e.getMessage() != null ? e.getMessage() : "Invalid deflated zip entry data");
                }
            }

            /**
             * Copy stored (non-deflated) data from ByteBuffer to target buffer.
             *
             * @param buf
             *            the buffer to copy the stored entry into.
             * @param off
             *            the offset within buf to start writing.
             * @param len
             *            the number of bytes to read.
             * @return the number of bytes read.
             * @throws IOException
             *             if an I/O exception occurred.
             * @throws InterruptedException
             *             if the thread was interrupted.
             */
            private int readStored(final byte[] buf, final int off, final int len)
                    throws IOException, InterruptedException {
                int read = 0;
                while (read < len) {
                    if (!currChunkByteBuf.hasRemaining() && !readNextChunk()) {
                        return read == 0 ? -1 : read;
                    }
                    final int remainingToRead = len - read;
                    final int remainingInBuf = currChunkByteBuf.remaining();
                    final int numBytesRead = Math.min(remainingToRead, remainingInBuf);
                    try {
                        currChunkByteBuf.get(buf, off + read, numBytesRead);
                    } catch (final BufferUnderflowException e) {
                        // Should not happen
                        throw new EOFException("Unexpected EOF in stored (non-deflated) zip entry data");
                    }
                    read += numBytesRead;
                }
                return read;
            }

            @Override
            public int read(final byte[] buf, final int off, final int len) throws IOException {
                if (closed.get()) {
                    throw new IOException("Stream closed");
                }
                if (buf == null) {
                    throw new NullPointerException();
                } else if (off < 0 || len < 0 || len > buf.length - off) {
                    throw new IndexOutOfBoundsException();
                } else if (len == 0) {
                    return 0;
                } else if (parentLogicalZipFile.physicalZipFile.fileLen == 0) {
                    return -1;
                }
                try {
                    if (isDeflated) {
                        return readDeflated(buf, off, len);
                    } else {
                        return readStored(buf, off, len);
                    }
                } catch (final InterruptedException e) {
                    nestedJarHandler.interruptionChecker.interrupt();
                    throw new IOException("Thread was interrupted");
                }
            }

            @Override
            public int read() throws IOException {
                if (closed.get()) {
                    throw new IOException("Stream closed");
                }
                return read(scratch, 0, 1) == -1 ? -1 : scratch[0] & 0xff;
            }

            @Override
            public int available() throws IOException {
                if (closed.get()) {
                    throw new IOException("Stream closed");
                }
                if (inflater.finished()) {
                    eof = true;
                }
                return eof ? 0 : 1;
            }

            @Override
            public long skip(final long n) throws IOException {
                if (closed.get()) {
                    throw new IOException("Stream closed");
                }
                if (n < 0) {
                    throw new IllegalArgumentException("Invalid skip value");
                }
                long total = 0;
                while (total < n) {
                    final int numSkipped = read(scratch, 0, (int) Math.min(n - total, scratch.length));
                    if (numSkipped == -1) {
                        eof = true;
                        break;
                    }
                    total += numSkipped;
                }
                return total;
            }

            @Override
            public boolean markSupported() {
                return false;
            }

            @Override
            public synchronized void mark(final int readlimit) {
                throw new IllegalArgumentException("Not supported");
            }

            @Override
            public synchronized void reset() throws IOException {
                throw new IllegalArgumentException("Not supported");
            }

            @Override
            public void close() throws IOException {
                if (!closed.getAndSet(true)) {
                    currChunkByteBuf = null;
                    if (recyclableInflaterInstance != null) {
                        // Reset and recycle the Inflater
                        nestedJarHandler.inflaterRecycler.recycle(recyclableInflaterInstance);
                        recyclableInflaterInstance = null;
                    }
                }
            }
        };
    }
public InputStream open ( ) throws IOException , InterruptedException { if ( recyclableInflaterInstance != null ) { throw new IOException ( "Zip entry already open" ) ; } if ( isDeflated ) { recyclableInflaterInstance = nestedJarHandler . inflaterRecycler . acquire ( ) ; } return new InputStream ( ) { private final long dataStartOffsetWithinPhysicalZipFile = getEntryDataStartOffsetWithinPhysicalZipFile ( ) ; private final byte [ ] scratch = new byte [ 8192 ] ; private ByteBuffer currChunkByteBuf ; private boolean isLastChunk ; private int currChunkIdx ; private boolean eof ; private final Inflater inflater = isDeflated ? recyclableInflaterInstance . getInflater ( ) : null ; private final AtomicBoolean closed = new AtomicBoolean ( false ) ; private static final int INFLATE_BUF_SIZE = 1024 ; { currChunkIdx = ( int ) ( dataStartOffsetWithinPhysicalZipFile / FileUtils . MAX_BUFFER_SIZE ) ; currChunkByteBuf = parentLogicalZipFile . physicalZipFile . getByteBuffer ( currChunkIdx ) . duplicate ( ) ; final int chunkPos = ( int ) ( dataStartOffsetWithinPhysicalZipFile - ( ( ( long ) currChunkIdx ) * ( long ) FileUtils . MAX_BUFFER_SIZE ) ) ; ( ( Buffer ) currChunkByteBuf ) . position ( chunkPos ) ; final long endPos = chunkPos + compressedSize ; ( ( Buffer ) currChunkByteBuf ) . limit ( ( int ) Math . min ( FileUtils . MAX_BUFFER_SIZE , endPos ) ) ; isLastChunk = endPos <= FileUtils . MAX_BUFFER_SIZE ; } private boolean readNextChunk ( ) throws IOException , InterruptedException { currChunkIdx ++ ; if ( currChunkIdx >= parentLogicalZipFile . physicalZipFile . numMappedByteBuffers ) { return false ; } final long chunkStartOff = ( ( long ) currChunkIdx ) * ( long ) FileUtils . MAX_BUFFER_SIZE ; final long priorBytes = chunkStartOff - dataStartOffsetWithinPhysicalZipFile ; final long remainingBytes = compressedSize - priorBytes ; if ( remainingBytes <= 0 ) { return false ; } currChunkByteBuf = parentLogicalZipFile . physicalZipFile . getByteBuffer ( currChunkIdx ) . duplicate ( ) ; ( ( Buffer ) currChunkByteBuf ) . position ( 0 ) ; ( ( Buffer ) currChunkByteBuf ) . limit ( ( int ) Math . min ( FileUtils . MAX_BUFFER_SIZE , remainingBytes ) ) ; isLastChunk = remainingBytes <= FileUtils . MAX_BUFFER_SIZE ; return true ; } private int readDeflated ( final byte [ ] buf , final int off , final int len ) throws IOException , InterruptedException { try { final byte [ ] inflateBuf = new byte [ INFLATE_BUF_SIZE ] ; int numInflatedBytes ; while ( ( numInflatedBytes = inflater . inflate ( buf , off , len ) ) == 0 ) { if ( inflater . finished ( ) || inflater . needsDictionary ( ) ) { eof = true ; return - 1 ; } if ( inflater . needsInput ( ) ) { if ( ! currChunkByteBuf . hasRemaining ( ) && ! ( readNextChunk ( ) && currChunkByteBuf . hasRemaining ( ) ) ) { throw new IOException ( "Unexpected EOF in deflated data" ) ; } try { final int remaining = currChunkByteBuf . remaining ( ) ; if ( isLastChunk && remaining < inflateBuf . length ) { currChunkByteBuf . get ( inflateBuf , 0 , remaining ) ; inflateBuf [ remaining ] = ( byte ) 0 ; inflater . setInput ( inflateBuf , 0 , remaining + 1 ) ; } else if ( isLastChunk && remaining == inflateBuf . length ) { currChunkByteBuf . get ( inflateBuf , 0 , remaining - 1 ) ; inflater . setInput ( inflateBuf , 0 , remaining - 1 ) ; } else { final int bytesToRead = Math . min ( inflateBuf . length , remaining ) ; currChunkByteBuf . get ( inflateBuf , 0 , bytesToRead ) ; inflater . setInput ( inflateBuf , 0 , bytesToRead ) ; } } catch ( final BufferUnderflowException e ) { throw new IOException ( "Unexpected EOF in deflated data" ) ; } } } return numInflatedBytes ; } catch ( final DataFormatException e ) { throw new ZipException ( e . getMessage ( ) != null ? e . getMessage ( ) : "Invalid deflated zip entry data" ) ; } } private int readStored ( final byte [ ] buf , final int off , final int len ) throws IOException , InterruptedException { int read = 0 ; while ( read < len ) { if ( ! currChunkByteBuf . hasRemaining ( ) && ! readNextChunk ( ) ) { return read == 0 ? - 1 : read ; } final int remainingToRead = len - read ; final int remainingInBuf = currChunkByteBuf . remaining ( ) ; final int numBytesRead = Math . min ( remainingToRead , remainingInBuf ) ; try { currChunkByteBuf . get ( buf , off + read , numBytesRead ) ; } catch ( final BufferUnderflowException e ) { throw new EOFException ( "Unexpected EOF in stored (non-deflated) zip entry data" ) ; } read += numBytesRead ; } return read ; } @ Override public int read ( final byte [ ] buf , final int off , final int len ) throws IOException { if ( closed . get ( ) ) { throw new IOException ( "Stream closed" ) ; } if ( buf == null ) { throw new NullPointerException ( ) ; } else if ( off < 0 || len < 0 || len > buf . length - off ) { throw new IndexOutOfBoundsException ( ) ; } else if ( len == 0 ) { return 0 ; } else if ( parentLogicalZipFile . physicalZipFile . fileLen == 0 ) { return - 1 ; } try { if ( isDeflated ) { return readDeflated ( buf , off , len ) ; } else { return readStored ( buf , off , len ) ; } } catch ( final InterruptedException e ) { nestedJarHandler . interruptionChecker . interrupt ( ) ; throw new IOException ( "Thread was interrupted" ) ; } } @ Override public int read ( ) throws IOException { if ( closed . get ( ) ) { throw new IOException ( "Stream closed" ) ; } return read ( scratch , 0 , 1 ) == - 1 ? - 1 : scratch [ 0 ] & 0xff ; } @ Override public int available ( ) throws IOException { if ( closed . get ( ) ) { throw new IOException ( "Stream closed" ) ; } if ( inflater . finished ( ) ) { eof = true ; } return eof ? 0 : 1 ; } @ Override public long skip ( final long n ) throws IOException { if ( closed . get ( ) ) { throw new IOException ( "Stream closed" ) ; } if ( n < 0 ) { throw new IllegalArgumentException ( "Invalid skip value" ) ; } long total = 0 ; while ( total < n ) { final int numSkipped = read ( scratch , 0 , ( int ) Math . min ( n - total , scratch . length ) ) ; if ( numSkipped == - 1 ) { eof = true ; break ; } total += numSkipped ; } return total ; } @ Override public boolean markSupported ( ) { return false ; } @ Override public synchronized void mark ( final int readlimit ) { throw new IllegalArgumentException ( "Not supported" ) ; } @ Override public synchronized void reset ( ) throws IOException { throw new IllegalArgumentException ( "Not supported" ) ; } @ Override public void close ( ) throws IOException { if ( ! closed . getAndSet ( true ) ) { currChunkByteBuf = null ; if ( recyclableInflaterInstance != null ) { nestedJarHandler . inflaterRecycler . recycle ( recyclableInflaterInstance ) ; recyclableInflaterInstance = null ; } } } } ; }
Open the data of the zip entry as an {@link InputStream}, inflating the data if the entry is deflated.

@return the input stream
@throws IOException
If an I/O exception occurs.
@throws InterruptedException
if the thread was interrupted.