< prev index next >

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

Print this page

        

*** 24,87 **** */ package java.util.zip; import java.io.Closeable; - 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. * * <p> Unless otherwise noted, passing a {@code null} argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * * @author David Connelly * @since 1.1 */ 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. --- 24,106 ---- */ package java.util.zip; import java.io.Closeable; import java.io.EOFException; import java.io.File; + import java.io.IOException; + import java.io.InputStream; 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.Files; ! import java.nio.file.attribute.BasicFileAttributes; 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.NoSuchElementException; + import java.util.Objects; + 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. * * <p> Unless otherwise noted, passing a {@code null} argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * + * <p> + * @apiNote + * In earlier versions, the {@link Object#finalize} method was overridden and + * specified to close the ZipFile object and release its system resource when + * the instance becomes unreachable. The {@code finalize} method is no longer + * defined. 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. + * + * @implNote + * The resources held by this object will be released when the instance becomes + * phantom-reachable, if the {@code close} is not invoked explicitly. + * <p> + * * @author David Connelly * @since 1.1 */ 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(); } /** --- 227,243 ---- 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 --- 300,313 ---- * @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 --- 320,337 ---- 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 **** --- 346,357 ---- */ 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: --- 363,374 ---- 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,370 **** 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"); } --- 377,389 ---- 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"); }
*** 372,402 **** } 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. --- 391,424 ---- } 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.getInflater(), res, size); ! } ! ! private ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, ! CleanableResource res, 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; ! synchronized (res.istreams) { ! res.istreams.remove(this); } + cleanable.clean(); + // do this last as it may throw IOException + super.close(); } // 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() { --- 436,451 ---- } 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(); --- 457,467 ---- 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; --- 480,490 ---- 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) { --- 519,529 ---- 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,673 **** * @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 { if (closeRequested) { throw new ZipException("ZipFile closed"); --- 568,719 ---- * @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 { ! private final Cleanable cleanable; ! ! // The outstanding inputstreams that need to be closed ! final Set<InputStream> istreams; ! ! // List of cached Inflater objects for decompression, ! // set to null when closed ! private Deque<Inflater> inflaters; ! ! // zip source ! final 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.inflaters = new ArrayDeque<>(); ! this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0); } ! void clean() throws IOException { ! try { ! cleanable.clean(); ! } catch (UncheckedIOException e) { ! // unwrap UncheckedIOException and re-throw it ! throw e.getCause(); ! } ! } ! ! Inflater getInflater() { ! Deque<Inflater> inflaters = this.inflaters; ! if (inflaters != null) { ! synchronized (inflaters) { ! // double checked! ! if (this.inflaters == inflaters) { ! Inflater inf = inflaters.poll(); if (inf != null) { ! return inf; } } } } ! // inflaters cache already closed or empty - allocate new ! return new Inflater(true); ! } ! ! void releaseInflater(Inflater inf) { ! Deque<Inflater> inflaters = this.inflaters; ! if (inflaters != null) { ! synchronized (inflaters) { ! // double checked! ! if (this.inflaters == inflaters) { ! inf.reset(); ! inflaters.add(inf); ! return; ! } ! } ! } ! // inflaters cache already closed - just end late comers ! inf.end(); ! } ! ! public void run() { ! // Release cached inflaters and close inflaters cache 1st ! Deque<Inflater> inflaters = this.inflaters; ! if (inflaters != null) { ! synchronized (inflaters) { ! // no need to double-check as only one thread gets a chance ! // to execute run() (Cleaner guarantee)... Inflater inf; ! while ((inf = inflaters.poll()) != null) { inf.end(); } + // close inflaters cache + this.inflaters = null; + } + } + // collect IOException(s)... + IOException ioe = null; + // close streams + 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 zip src if (zsrc != null) { ! synchronized (zsrc) { ! try { ! Source.release(zsrc); ! } catch (IOException e) { ! if (ioe == null) ioe = e; else ioe.addSuppressed(e); ! } ! } ! } ! // throw possible IOException wrapped into unchecked ! 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 ! res.clean(); ! } } private void ensureOpen() { if (closeRequested) { throw new IllegalStateException("zip file closed"); } } private void ensureOpenOrZipException() throws IOException { if (closeRequested) { throw new ZipException("ZipFile closed");
*** 692,702 **** 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); --- 738,748 ---- 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) throws IOException { int off = cenpos + CENHDR + CENNAM(cen, cenpos); int end = off + CENEXT(cen, cenpos);
*** 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)"); --- 787,797 ---- */ 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; } } --- 812,822 ---- 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 --- 863,877 ---- 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 **** --- 879,889 ---- * 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(); } } --- 899,909 ---- 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); --- 987,997 ---- } } 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(); } } } --- 1012,1024 ---- 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(); } } }
< prev index next >