src/java.base/share/classes/java/util/zip/ZipFile.java

Print this page

        

*** 29,66 **** import java.io.InputStream; import java.io.IOException; import java.io.EOFException; import java.io.File; import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.attribute.BasicFileAttributes; - import java.nio.file.Path; import java.nio.file.Files; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; - import java.util.Map; import java.util.Objects; import java.util.NoSuchElementException; import java.util.Spliterator; import java.util.Spliterators; import java.util.WeakHashMap; import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.misc.JavaUtilZipFileAccess; import jdk.internal.misc.SharedSecrets; - import jdk.internal.misc.JavaIORandomAccessFileAccess; import jdk.internal.misc.VM; import jdk.internal.perf.PerfCounter; - import static java.util.zip.ZipConstants.*; import static java.util.zip.ZipConstants64.*; import static java.util.zip.ZipUtils.*; /** * This class is used to read entries from a zip file. --- 29,67 ---- import java.io.InputStream; import java.io.IOException; import java.io.EOFException; import java.io.File; import java.io.RandomAccessFile; + import java.io.UncheckedIOException; + import java.lang.ref.Cleaner.Cleanable; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.Files; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; + import java.util.Collections; import java.util.Deque; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Objects; import java.util.NoSuchElementException; + import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; import java.util.WeakHashMap; import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.misc.JavaUtilZipFileAccess; import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.VM; import jdk.internal.perf.PerfCounter; + import jdk.internal.ref.CleanerFactory; import static java.util.zip.ZipConstants64.*; import static java.util.zip.ZipUtils.*; /** * This class is used to read entries from a zip file.
*** 75,87 **** public class ZipFile implements ZipConstants, Closeable { private final String name; // zip file name private volatile boolean closeRequested; - private Source zsrc; private ZipCoder zc; private static final int STORED = ZipEntry.STORED; private static final int DEFLATED = ZipEntry.DEFLATED; /** * Mode flag to open a zip file for reading. --- 76,94 ---- public class ZipFile implements ZipConstants, Closeable { private final String name; // zip file name private volatile boolean closeRequested; private ZipCoder zc; + // The "resource" used by this zip file that needs to be + // cleaned after use. + // a) the input streams that need to be closed + // b) the list of cached Inflater objects + // c) the "native" source of this zip file. + private final CleanableResource res; + private static final int STORED = ZipEntry.STORED; private static final int DEFLATED = ZipEntry.DEFLATED; /** * Mode flag to open a zip file for reading.
*** 208,221 **** if ((mode & OPEN_DELETE) != 0) { sm.checkDelete(name); } } Objects.requireNonNull(charset, "charset"); this.zc = ZipCoder.get(charset); this.name = name; long t0 = System.nanoTime(); ! this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0); PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); PerfCounter.getZipFileCount().increment(); } /** --- 215,231 ---- if ((mode & OPEN_DELETE) != 0) { sm.checkDelete(name); } } Objects.requireNonNull(charset, "charset"); + this.zc = ZipCoder.get(charset); this.name = name; long t0 = System.nanoTime(); ! ! this.res = new CleanableResource(this, file, mode); ! PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); PerfCounter.getZipFileCount().increment(); } /**
*** 278,291 **** * @since 1.7 */ public String getComment() { synchronized (this) { ensureOpen(); ! if (zsrc.comment == null) { return null; } ! return zc.toString(zsrc.comment); } } /** * Returns the zip file entry for the specified name, or null --- 288,301 ---- * @since 1.7 */ public String getComment() { synchronized (this) { ensureOpen(); ! if (res.zsrc.comment == null) { return null; } ! return zc.toString(res.zsrc.comment); } } /** * Returns the zip file entry for the specified name, or null
*** 298,319 **** public ZipEntry getEntry(String name) { Objects.requireNonNull(name, "name"); synchronized (this) { ensureOpen(); byte[] bname = zc.getBytes(name); ! int pos = zsrc.getEntryPos(bname, true); if (pos != -1) { return getZipEntry(name, bname, pos); } } return null; } - // The outstanding inputstreams that need to be closed, - // mapped to the inflater objects they use. - private final Map<InputStream, Inflater> streams = new WeakHashMap<>(); - /** * Returns an input stream for reading the contents of the specified * zip file entry. * <p> * Closing this ZIP file will, in turn, close all input streams that --- 308,325 ---- public ZipEntry getEntry(String name) { Objects.requireNonNull(name, "name"); synchronized (this) { ensureOpen(); byte[] bname = zc.getBytes(name); ! int pos = res.zsrc.getEntryPos(bname, true); if (pos != -1) { return getZipEntry(name, bname, pos); } } return null; } /** * Returns an input stream for reading the contents of the specified * zip file entry. * <p> * Closing this ZIP file will, in turn, close all input streams that
*** 328,337 **** --- 334,345 ---- */ public InputStream getInputStream(ZipEntry entry) throws IOException { Objects.requireNonNull(entry, "entry"); int pos = -1; ZipFileInputStream in = null; + Source zsrc = res.zsrc; + Set<InputStream> istreams = res.istreams; synchronized (this) { ensureOpen(); if (Objects.equals(lastEntryName, entry.name)) { pos = lastEntryPos; } else if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
*** 343,354 **** return null; } in = new ZipFileInputStream(zsrc.cen, pos); switch (CENHOW(zsrc.cen, pos)) { case STORED: ! synchronized (streams) { ! streams.put(in, null); } return in; case DEFLATED: // Inflater likes a bit of slack // MORE: Compute good size for inflater stream: --- 351,362 ---- return null; } in = new ZipFileInputStream(zsrc.cen, pos); switch (CENHOW(zsrc.cen, pos)) { case STORED: ! synchronized (istreams) { ! istreams.add(in); } return in; case DEFLATED: // Inflater likes a bit of slack // MORE: Compute good size for inflater stream:
*** 357,402 **** size = 8192; } if (size <= 0) { size = 4096; } ! Inflater inf = getInflater(); ! InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size); ! synchronized (streams) { ! streams.put(is, inf); } return is; default: throw new ZipException("invalid compression method"); } } } private class ZipFileInflaterInputStream extends InflaterInputStream { private volatile boolean closeRequested; private boolean eof = false; ! private final ZipFileInputStream zfin; ! ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, ! int size) { super(zfin, inf, size); ! this.zfin = zfin; } public void close() throws IOException { if (closeRequested) return; closeRequested = true; - super.close(); ! Inflater inf; ! synchronized (streams) { ! inf = streams.remove(this); ! } ! if (inf != null) { ! releaseInflater(inf); } } // Override fill() method to provide an extra "dummy" byte // at the end of the input stream. This is required when // using the "nowrap" Inflater option. --- 365,413 ---- size = 8192; } if (size <= 0) { size = 4096; } ! InputStream is = new ZipFileInflaterInputStream(in, res, (int)size); ! synchronized (istreams) { ! istreams.add(is); } return is; default: throw new ZipException("invalid compression method"); } } + } private class ZipFileInflaterInputStream extends InflaterInputStream { private volatile boolean closeRequested; private boolean eof = false; ! private final Cleanable cleanable; ! ZipFileInflaterInputStream(ZipFileInputStream zfin, ! CleanableResource res, int size) { ! this(zfin, res, res.getInflater(), size); ! } ! ! private ZipFileInflaterInputStream(ZipFileInputStream zfin, ! CleanableResource res, ! Inflater inf, int size) { super(zfin, inf, size); ! this.cleanable = CleanerFactory.cleaner().register(this, ! () -> res.releaseInflater(inf)); } public void close() throws IOException { if (closeRequested) return; closeRequested = true; super.close(); ! synchronized (res.istreams) { ! res.istreams.remove(this); } + cleanable.clean(); } // Override fill() method to provide an extra "dummy" byte // at the end of the input stream. This is required when // using the "nowrap" Inflater option.
*** 414,465 **** } public int available() throws IOException { if (closeRequested) return 0; ! long avail = zfin.size() - inf.getBytesWritten(); return (avail > (long) Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail); } - - @SuppressWarnings("deprecation") - protected void finalize() throws Throwable { - close(); - } - } - - /* - * Gets an inflater from the list of available inflaters or allocates - * a new one. - */ - private Inflater getInflater() { - Inflater inf; - synchronized (inflaterCache) { - while ((inf = inflaterCache.poll()) != null) { - if (!inf.ended()) { - return inf; - } - } - } - return new Inflater(true); - } - - /* - * Releases the specified inflater to the list of available inflaters. - */ - private void releaseInflater(Inflater inf) { - if (!inf.ended()) { - inf.reset(); - synchronized (inflaterCache) { - inflaterCache.add(inf); - } - } } - // List of available Inflater objects for decompression - private final Deque<Inflater> inflaterCache = new ArrayDeque<>(); - /** * Returns the path name of the ZIP file. * @return the path name of the ZIP file */ public String getName() { --- 425,440 ---- } public int available() throws IOException { if (closeRequested) return 0; ! long avail = ((ZipFileInputStream)in).size() - inf.getBytesWritten(); return (avail > (long) Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail); } } /** * Returns the path name of the ZIP file. * @return the path name of the ZIP file */ public String getName() {
*** 471,481 **** private final int entryCount; public ZipEntryIterator() { synchronized (ZipFile.this) { ensureOpen(); ! this.entryCount = zsrc.total; } } public boolean hasMoreElements() { return hasNext(); --- 446,456 ---- private final int entryCount; public ZipEntryIterator() { synchronized (ZipFile.this) { ensureOpen(); ! this.entryCount = res.zsrc.total; } } public boolean hasMoreElements() { return hasNext();
*** 494,504 **** ensureOpen(); if (!hasNext()) { throw new NoSuchElementException(); } // each "entry" has 3 ints in table entries ! return getZipEntry(null, null, zsrc.getEntryPos(i++ * 3)); } } public Iterator<ZipEntry> asIterator() { return this; --- 469,479 ---- ensureOpen(); if (!hasNext()) { throw new NoSuchElementException(); } // each "entry" has 3 ints in table entries ! return getZipEntry(null, null, res.zsrc.getEntryPos(i++ * 3)); } } public Iterator<ZipEntry> asIterator() { return this;
*** 533,543 **** private String lastEntryName; private int lastEntryPos; /* Checks ensureOpen() before invoke this method */ private ZipEntry getZipEntry(String name, byte[] bname, int pos) { ! byte[] cen = zsrc.cen; int nlen = CENNAM(cen, pos); int elen = CENEXT(cen, pos); int clen = CENCOM(cen, pos); int flag = CENFLG(cen, pos); if (name == null || bname.length != nlen) { --- 508,518 ---- private String lastEntryName; private int lastEntryPos; /* Checks ensureOpen() before invoke this method */ private ZipEntry getZipEntry(String name, byte[] bname, int pos) { ! byte[] cen = res.zsrc.cen; int nlen = CENNAM(cen, pos); int elen = CENEXT(cen, pos); int clen = CENCOM(cen, pos); int flag = CENFLG(cen, pos); if (name == null || bname.length != nlen) {
*** 582,671 **** * @throws IllegalStateException if the zip file has been closed */ public int size() { synchronized (this) { ensureOpen(); ! return zsrc.total; } } ! /** ! * Closes the ZIP file. ! * <p> Closing this ZIP file will close all of the input streams ! * previously returned by invocations of the {@link #getInputStream ! * getInputStream} method. ! * ! * @throws IOException if an I/O error has occurred */ ! public void close() throws IOException { ! if (closeRequested) { ! return; } - closeRequested = true; ! synchronized (this) { // Close streams, release their inflaters ! synchronized (streams) { ! if (!streams.isEmpty()) { ! Map<InputStream, Inflater> copy = new HashMap<>(streams); ! streams.clear(); ! for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { ! e.getKey().close(); ! Inflater inf = e.getValue(); ! if (inf != null) { ! inf.end(); } } } } // Release cached inflaters synchronized (inflaterCache) { Inflater inf; while ((inf = inflaterCache.poll()) != null) { inf.end(); } } // Release zip src if (zsrc != null) { ! Source.close(zsrc); zsrc = null; } } } /** * Ensures that the system resources held by this ZipFile object are * released when there are no more references to it. * ! * <p> ! * Since the time when GC would invoke this method is undetermined, ! * it is strongly recommended that applications invoke the {@code close} ! * method as soon they have finished accessing this {@code ZipFile}. ! * This will prevent holding up system resources for an undetermined ! * length of time. ! * ! * @deprecated The {@code finalize} method has been deprecated. ! * Subclasses that override {@code finalize} in order to perform cleanup ! * should be modified to use alternative cleanup mechanisms and ! * to remove the overriding {@code finalize} method. ! * When overriding the {@code finalize} method, its implementation must explicitly ! * ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}. ! * See the specification for {@link Object#finalize()} for further ! * information about migration options. * @throws IOException if an I/O error has occurred - * @see java.util.zip.ZipFile#close() */ ! @Deprecated(since="9") ! protected void finalize() throws IOException { ! close(); ! } private void ensureOpen() { if (closeRequested) { throw new IllegalStateException("zip file closed"); } ! if (zsrc == null) { throw new IllegalStateException("The object is not initialized."); } } private void ensureOpenOrZipException() throws IOException { --- 557,712 ---- * @throws IllegalStateException if the zip file has been closed */ public int size() { synchronized (this) { ensureOpen(); ! return res.zsrc.total; } } ! private static class CleanableResource implements Runnable { ! // The outstanding inputstreams that need to be closed ! final Set<InputStream> istreams; ! ! // List of cached Inflater objects for decompression ! final Deque<Inflater> inflaterCache; ! ! final Cleanable cleanable; ! ! Source zsrc; ! ! CleanableResource(ZipFile zf, File file, int mode) throws IOException { ! this.cleanable = CleanerFactory.cleaner().register(zf, this); ! this.istreams = Collections.newSetFromMap(new WeakHashMap<>()); ! this.inflaterCache = new ArrayDeque<>(); ! this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0); ! } ! ! void clean() { ! cleanable.clean(); ! } ! ! /* ! * Gets an inflater from the list of available inflaters or allocates ! * a new one. */ ! Inflater getInflater() { ! Inflater inf; ! synchronized (inflaterCache) { ! if ((inf = inflaterCache.poll()) != null) { ! return inf; ! } ! } ! return new Inflater(true); } ! /* ! * Releases the specified inflater to the list of available inflaters. ! */ ! void releaseInflater(Inflater inf) { ! inf.reset(); ! synchronized (inflaterCache) { ! inflaterCache.add(inf); ! } ! } ! ! public void run() { ! IOException ioe = null; // Close streams, release their inflaters ! if (istreams != null) { ! synchronized (istreams) { ! if (!istreams.isEmpty()) { ! InputStream[] copy = istreams.toArray(new InputStream[0]); ! istreams.clear(); ! for (InputStream is : copy) { ! try { ! is.close(); ! } catch (IOException e) { ! if (ioe == null) ioe = e; ! else ioe.addSuppressed(e); ! } } } } } // Release cached inflaters + if (inflaterCache != null) { synchronized (inflaterCache) { Inflater inf; while ((inf = inflaterCache.poll()) != null) { inf.end(); } } + } // Release zip src if (zsrc != null) { ! synchronized (zsrc) { ! try { ! Source.release(zsrc); zsrc = null; + } catch (IOException e) { + if (ioe == null) ioe = e; + else ioe.addSuppressed(e); + } + } + } + if (ioe != null) { + throw new UncheckedIOException(ioe); + } + } + } + + /** + * Closes the ZIP file. + * + * <p> Closing this ZIP file will close all of the input streams + * previously returned by invocations of the {@link #getInputStream + * getInputStream} method. + * + * @throws IOException if an I/O error has occurred + */ + public void close() throws IOException { + if (closeRequested) { + return; + } + closeRequested = true; + + synchronized (this) { + // Close streams, release their inflaters, release cached inflaters + // and release zip source + try { + res.clean(); + } catch (UncheckedIOException ioe) { + throw ioe.getCause(); } } } /** * Ensures that the system resources held by this ZipFile object are * released when there are no more references to it. * ! * @deprecated The {@code finalize} method has been deprecated and ! * implemented as a no-op. Subclasses that override {@code finalize} ! * in order to perform cleanup should be modified to use alternative ! * cleanup mechanisms and to remove the overriding {@code finalize} ! * method. The recommended cleanup for ZipFile object is to explicitly ! * invoke {@code close} method when it is no longer in use, or use ! * try-with-resources. If the {@code close} is not invoked explicitly ! * the resources held by this object will be released when the instance ! * becomes phantom-reachable. ! * * @throws IOException if an I/O error has occurred */ ! @Deprecated(since="9", forRemoval=true) ! protected void finalize() throws IOException {} private void ensureOpen() { if (closeRequested) { throw new IllegalStateException("zip file closed"); } ! if (res.zsrc == null) { throw new IllegalStateException("The object is not initialized."); } } private void ensureOpenOrZipException() throws IOException {
*** 682,705 **** private volatile boolean closeRequested; private long pos; // current position within entry data protected long rem; // number of remaining bytes within entry protected long size; // uncompressed size of this entry ! ZipFileInputStream(byte[] cen, int cenpos) throws IOException { rem = CENSIZ(cen, cenpos); size = CENLEN(cen, cenpos); pos = CENOFF(cen, cenpos); // zip64 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || pos == ZIP64_MAGICVAL) { checkZIP64(cen, cenpos); } // negative for lazy initialization, see getDataOffset(); ! pos = - (pos + ZipFile.this.zsrc.locpos); } ! private void checkZIP64(byte[] cen, int cenpos) throws IOException { int off = cenpos + CENHDR + CENNAM(cen, cenpos); int end = off + CENEXT(cen, cenpos); while (off + 4 < end) { int tag = get16(cen, off); int sz = get16(cen, off + 2); --- 723,746 ---- private volatile boolean closeRequested; private long pos; // current position within entry data protected long rem; // number of remaining bytes within entry protected long size; // uncompressed size of this entry ! ZipFileInputStream(byte[] cen, int cenpos) { rem = CENSIZ(cen, cenpos); size = CENLEN(cen, cenpos); pos = CENOFF(cen, cenpos); // zip64 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || pos == ZIP64_MAGICVAL) { checkZIP64(cen, cenpos); } // negative for lazy initialization, see getDataOffset(); ! pos = - (pos + ZipFile.this.res.zsrc.locpos); } ! private void checkZIP64(byte[] cen, int cenpos) { int off = cenpos + CENHDR + CENNAM(cen, cenpos); int end = off + CENEXT(cen, cenpos); while (off + 4 < end) { int tag = get16(cen, off); int sz = get16(cen, off + 2);
*** 741,751 **** */ private long initDataOffset() throws IOException { if (pos <= 0) { byte[] loc = new byte[LOCHDR]; pos = -pos; ! int len = ZipFile.this.zsrc.readFullyAt(loc, 0, loc.length, pos); if (len != LOCHDR) { throw new ZipException("ZipFile error reading zip file"); } if (LOCSIG(loc) != LOCSIG) { throw new ZipException("ZipFile invalid LOC header (bad signature)"); --- 782,792 ---- */ private long initDataOffset() throws IOException { if (pos <= 0) { byte[] loc = new byte[LOCHDR]; pos = -pos; ! int len = ZipFile.this.res.zsrc.readFullyAt(loc, 0, loc.length, pos); if (len != LOCHDR) { throw new ZipException("ZipFile error reading zip file"); } if (LOCSIG(loc) != LOCSIG) { throw new ZipException("ZipFile invalid LOC header (bad signature)");
*** 766,776 **** len = (int) rem; } if (len <= 0) { return 0; } ! len = ZipFile.this.zsrc.readAt(b, off, len, pos); if (len > 0) { pos += len; rem -= len; } } --- 807,817 ---- len = (int) rem; } if (len <= 0) { return 0; } ! len = ZipFile.this.res.zsrc.readAt(b, off, len, pos); if (len > 0) { pos += len; rem -= len; } }
*** 817,835 **** if (closeRequested) { return; } closeRequested = true; rem = 0; ! synchronized (streams) { ! streams.remove(this); } } - @SuppressWarnings("deprecation") - protected void finalize() { - close(); - } } /** * Returns the names of all non-directory entries that begin with * "META-INF/" (case ignored). This method is used in JarFile, via --- 858,872 ---- if (closeRequested) { return; } closeRequested = true; rem = 0; ! synchronized (res.istreams) { ! res.istreams.remove(this); } } } /** * Returns the names of all non-directory entries that begin with * "META-INF/" (case ignored). This method is used in JarFile, via
*** 837,846 **** --- 874,884 ---- * signature file entries. Returns null if no entries were found. */ private String[] getMetaInfEntryNames() { synchronized (this) { ensureOpen(); + Source zsrc = res.zsrc; if (zsrc.metanames == null) { return null; } String[] names = new String[zsrc.metanames.length]; byte[] cen = zsrc.cen;
*** 856,866 **** private static boolean isWindows; static { SharedSecrets.setJavaUtilZipFileAccess( new JavaUtilZipFileAccess() { public boolean startsWithLocHeader(ZipFile zip) { ! return zip.zsrc.startsWithLoc; } public String[] getMetaInfEntryNames(ZipFile zip) { return zip.getMetaInfEntryNames(); } } --- 894,904 ---- private static boolean isWindows; static { SharedSecrets.setJavaUtilZipFileAccess( new JavaUtilZipFileAccess() { public boolean startsWithLocHeader(ZipFile zip) { ! return zip.res.zsrc.startsWithLoc; } public String[] getMetaInfEntryNames(ZipFile zip) { return zip.getMetaInfEntryNames(); } }
*** 944,954 **** } } private static final HashMap<Key, Source> files = new HashMap<>(); ! public static Source get(File file, boolean toDelete) throws IOException { Key key = new Key(file, Files.readAttributes(file.toPath(), BasicFileAttributes.class)); Source src = null; synchronized (files) { src = files.get(key); --- 982,992 ---- } } private static final HashMap<Key, Source> files = new HashMap<>(); ! static Source get(File file, boolean toDelete) throws IOException { Key key = new Key(file, Files.readAttributes(file.toPath(), BasicFileAttributes.class)); Source src = null; synchronized (files) { src = files.get(key);
*** 969,981 **** files.put(key, src); return src; } } ! private static void close(Source src) throws IOException { synchronized (files) { ! if (--src.refs == 0) { files.remove(src.key); src.close(); } } } --- 1007,1019 ---- files.put(key, src); return src; } } ! static void release(Source src) throws IOException { synchronized (files) { ! if (src != null && --src.refs == 0) { files.remove(src.key); src.close(); } } }