@@ -17,14 +17,13 @@ package io
17
17
import java .net .URL
18
18
import java .io .{ByteArrayInputStream , FilterInputStream , IOException , InputStream }
19
19
import java .io .{File => JFile }
20
+ import java .util .concurrent .{ArrayBlockingQueue , TimeUnit }
20
21
import java .util .zip .{ZipEntry , ZipFile , ZipInputStream }
21
22
import java .util .jar .Manifest
22
-
23
23
import scala .collection .mutable
24
24
import scala .collection .JavaConverters ._
25
25
import scala .annotation .tailrec
26
26
import scala .reflect .internal .JDK9Reflectors
27
-
28
27
import ZipArchive ._
29
28
30
29
/** An abstraction for zip files and streams. Everything is written the way
@@ -146,6 +145,31 @@ abstract class ZipArchive(override val file: JFile, release: Option[String]) ext
146
145
/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
147
146
final class FileZipArchive (file : JFile , release : Option [String ]) extends ZipArchive (file, release) {
148
147
def this (file : JFile ) = this (file, None )
148
+ private object zipFilePool {
149
+ private [this ] val zipFiles = new ArrayBlockingQueue [ZipFile ](Runtime .getRuntime.availableProcessors())
150
+
151
+ def acquire : ZipFile = {
152
+ val zf = zipFiles.poll(0 , TimeUnit .MILLISECONDS )
153
+ zf match {
154
+ case null =>
155
+ openZipFile()
156
+ case _ =>
157
+ zf
158
+ }
159
+ }
160
+
161
+ def release (zf : ZipFile ): Unit = {
162
+ if (! zipFiles.offer(zf, 0 , TimeUnit .MILLISECONDS ))
163
+ zf.close()
164
+ }
165
+
166
+ def close (): Unit = {
167
+ val zipFilesToClose = new java.util.ArrayList [ZipFile ]
168
+ zipFiles.drainTo(zipFilesToClose)
169
+ zipFilesToClose.iterator().forEachRemaining(_.close())
170
+ }
171
+ }
172
+
149
173
private [this ] def openZipFile (): ZipFile = try {
150
174
release match {
151
175
case Some (r) if file.getName.endsWith(" .jar" ) =>
@@ -175,18 +199,24 @@ final class FileZipArchive(file: JFile, release: Option[String]) extends ZipArch
175
199
override def sizeOption : Option [Int ] = Some (size) // could be stale
176
200
}
177
201
178
- // keeps a file handle open to ZipFile, which forbids file mutation
179
- // on Windows, and leaks memory on all OS (typically by stopping
180
- // classloaders from being garbage collected). But is slightly
181
- // faster than LazyEntry.
202
+ // keeps file handle(s) open to ZipFile in the pool this.zipFiles,
203
+ // which forbids file mutation on Windows, and leaks memory on all OS (typically by stopping
204
+ // classloaders from being garbage collected). But is slightly faster than LazyEntry.
182
205
private [this ] class LeakyEntry (
183
- zipFile : ZipFile ,
184
- zipEntry : ZipEntry ,
185
- name : String
206
+ name : String ,
207
+ time : Long ,
208
+ size : Int
186
209
) extends Entry (name) {
187
- override def lastModified : Long = zipEntry.getTime
188
- override def input : InputStream = zipFile.getInputStream(zipEntry)
189
- override def sizeOption : Option [Int ] = Some (zipEntry.getSize.toInt)
210
+ override def lastModified : Long = time // could be stale
211
+ override def input : InputStream = {
212
+ val zipFile = zipFilePool.acquire
213
+ val entry = zipFile.getEntry(name) // with `-release`, returns the correct version under META-INF/versions
214
+ val delegate = zipFile.getInputStream(entry)
215
+ new FilterInputStream (delegate) {
216
+ override def close (): Unit = { zipFilePool.release(zipFile) }
217
+ }
218
+ }
219
+ override def sizeOption : Option [Int ] = Some (size) // could be stale
190
220
}
191
221
192
222
private [this ] val dirs = new java.util.HashMap [String , DirEntry ]()
@@ -200,10 +230,6 @@ final class FileZipArchive(file: JFile, release: Option[String]) extends ZipArch
200
230
while (enum .hasMoreElements) {
201
231
val zipEntry = enum .nextElement
202
232
if (! zipEntry.getName.startsWith(" META-INF/versions/" )) {
203
- val zipEntryVersioned = if (release.isDefined) {
204
- // JARFile will return the entry for the corresponding release-dependent version here under META-INF/versions
205
- zipFile.getEntry(zipEntry.getName)
206
- } else zipEntry
207
233
if (! zipEntry.isDirectory) {
208
234
val dir = getDir(dirs, zipEntry)
209
235
val f =
@@ -213,15 +239,17 @@ final class FileZipArchive(file: JFile, release: Option[String]) extends ZipArch
213
239
zipEntry.getTime,
214
240
zipEntry.getSize.toInt)
215
241
else
216
- new LeakyEntry (zipFile, zipEntryVersioned, zipEntry.getName)
242
+ new LeakyEntry (zipEntry.getName,
243
+ zipEntry.getTime,
244
+ zipEntry.getSize.toInt)
217
245
218
246
dir.entries(f.name) = f
219
247
}
220
248
}
221
249
}
222
250
} finally {
223
- if (ZipArchive .closeZipFile) zipFile.close( )
224
- else closeables ::= zipFile
251
+ if (! ZipArchive .closeZipFile)
252
+ zipFilePool.release( zipFile)
225
253
}
226
254
root
227
255
}
@@ -242,9 +270,8 @@ final class FileZipArchive(file: JFile, release: Option[String]) extends ZipArch
242
270
case x : FileZipArchive => file.getAbsoluteFile == x.file.getAbsoluteFile
243
271
case _ => false
244
272
}
245
- private [this ] var closeables : List [java.io.Closeable ] = Nil
246
273
override def close (): Unit = {
247
- closeables.foreach(_. close)
274
+ zipFilePool. close( )
248
275
}
249
276
}
250
277
/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
0 commit comments