< 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 >