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