< prev index next >

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

Print this page

        

@@ -29,48 +29,64 @@
 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;
 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.Collections;
 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.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
 import java.util.WeakHashMap;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
 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 jdk.internal.ref.CleanerFactory;
 
-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.
  *
+ * <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 {

@@ -78,10 +94,19 @@
     private final String name;     // zip file name
     private volatile boolean closeRequested;
     private Source zsrc;
     private ZipCoder zc;
 
+    // The outstanding inputstreams that need to be closed
+    private final Set<InputStream> streams;
+
+    // List of cached Inflater objects for decompression
+    private final Deque<Inflater> inflaterCache;
+
+    // Cleanable reference to Source
+    private final Cleaner.CleanableResource<Source> zsrcRes;
+
     private static final int STORED = ZipEntry.STORED;
     private static final int DEFLATED = ZipEntry.DEFLATED;
 
     /**
      * Mode flag to open a zip file for reading.

@@ -211,11 +236,24 @@
         }
         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);
+        this.streams = Collections.newSetFromMap(new WeakHashMap<>());
+        this.inflaterCache = new ArrayDeque<>();
+        try {
+            this.zsrcRes = CleanerFactory
+                .cleaner()
+                .createResource(
+                    this,
+                    () -> Source.get(file, (mode & OPEN_DELETE) != 0),
+                    Source::release
+                );
+        } catch (UncheckedIOException e) {
+            throw e.getCause();
+        }
+        this.zsrc = zsrcRes.value();
         PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
         PerfCounter.getZipFileCount().increment();
     }
 
     /**

@@ -306,14 +344,10 @@
             }
         }
         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

@@ -344,11 +378,11 @@
             }
             in = new ZipFileInputStream(zsrc.cen, pos);
             switch (CENHOW(zsrc.cen, pos)) {
             case STORED:
                 synchronized (streams) {
-                    streams.put(in, null);
+                    streams.add(in);
                 }
                 return in;
             case DEFLATED:
                 // Inflater likes a bit of slack
                 // MORE: Compute good size for inflater stream:

@@ -357,14 +391,14 @@
                     size = 8192;
                 }
                 if (size <= 0) {
                     size = 4096;
                 }
-                Inflater inf = getInflater();
-                InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size);
+                InputStream is = new ZipFileInflaterInputStream(
+                    in, this::getInflater, this::releaseInflater, (int)size);
                 synchronized (streams) {
-                    streams.put(is, inf);
+                    streams.add(is);
                 }
                 return is;
             default:
                 throw new ZipException("invalid compression method");
             }

@@ -372,30 +406,26 @@
     }
 
     private class ZipFileInflaterInputStream extends InflaterInputStream {
         private volatile boolean closeRequested;
         private boolean eof = false;
-        private final ZipFileInputStream zfin;
 
-        ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
+        ZipFileInflaterInputStream(ZipFileInputStream zfin,
+                                   Supplier<Inflater> inflaterAllocator,
+                                   Consumer<Inflater> inflaterDeallocator,
                 int size) {
-            super(zfin, inf, size);
-            this.zfin = zfin;
+            super(zfin, inflaterAllocator, inflaterDeallocator, size);
         }
 
         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);
+                streams.remove(this);
             }
         }
 
         // Override fill() method to provide an extra "dummy" byte
         // at the end of the input stream. This is required when

@@ -414,51 +444,39 @@
         }
 
         public int available() throws IOException {
             if (closeRequested)
                 return 0;
-            long avail = zfin.size() - inf.getBytesWritten();
+            long avail = ((ZipFileInputStream) in).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
      */

@@ -594,27 +612,30 @@
      * 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;
-        }
+    public synchronized void close() throws IOException {
+        if (closeRequested) return;
         closeRequested = true;
 
-        synchronized (this) {
-            // Close streams, release their inflaters
+        IOException ioe = null;
+
+        // Close streams
             synchronized (streams) {
                 if (!streams.isEmpty()) {
-                    Map<InputStream, Inflater> copy = new HashMap<>(streams);
+                InputStream[] copy = streams.toArray(new InputStream[0]);
                     streams.clear();
-                    for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) {
-                        e.getKey().close();
-                        Inflater inf = e.getValue();
-                        if (inf != null) {
-                            inf.end();
+                for (InputStream is : copy) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                        if (ioe == null) {
+                            ioe = e;
+                        } else {
+                            ioe.addSuppressed(e);
+                        }
                         }
                     }
                 }
             }
             // Release cached inflaters

@@ -623,42 +644,22 @@
                 while ((inf = inflaterCache.poll()) != null) {
                     inf.end();
                 }
             }
             // Release zip src
-            if (zsrc != null) {
-                Source.close(zsrc);
-                zsrc = null;
+        try {
+            zsrcRes.clean();
+        } catch (UncheckedIOException e) {
+            if (ioe == null) {
+                ioe = e.getCause();
+            } else {
+                ioe.addSuppressed(e.getCause());
             }
         }
+        if (ioe != null) {
+            throw ioe;
     }
-
-    /**
-     * 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");

@@ -822,14 +823,10 @@
             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

@@ -944,11 +941,12 @@
             }
         }
         private static final HashMap<Key, Source> files = new HashMap<>();
 
 
-        public static Source get(File file, boolean toDelete) throws IOException {
+        static Source get(File file, boolean toDelete) {
+            try {
             Key key = new Key(file,
                               Files.readAttributes(file.toPath(), BasicFileAttributes.class));
             Source src = null;
             synchronized (files) {
                 src = files.get(key);

@@ -967,17 +965,24 @@
                     return src;
                 }
                 files.put(key, src);
                 return src;
             }
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
         }
 
-        private static void close(Source src) throws IOException {
+        static void release(Source src) {
             synchronized (files) {
                 if (--src.refs == 0) {
                     files.remove(src.key);
+                    try {
                     src.close();
+                    } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                    }
                 }
             }
         }
 
         private Source(Key key, boolean toDelete) throws IOException {
< prev index next >