Created
November 18, 2016 09:36
-
-
Save fommil/5709abc1678b346b6d927a13cd30b844 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright: 2010 - 2016 https://github.com/ensime/ensime-server/graphs | |
// License: http://www.gnu.org/licenses/gpl-3.0.en.html | |
/* NSC -- new Scala compiler | |
* Copyright 2005-2013 LAMP/EPFL | |
* @author Paul Phillips | |
*/ | |
package scala | |
package reflect | |
package io | |
import java.io.{ ByteArrayOutputStream, OutputStream } | |
import java.lang.reflect.Constructor | |
import java.net.URL | |
import java.io.{ IOException, InputStream, ByteArrayInputStream, FilterInputStream } | |
import java.io.{ File => JFile } | |
import java.util.zip._ | |
import java.util.jar.Manifest | |
import scala.collection.mutable | |
import scala.collection.convert.WrapAsScala.asScalaIterator | |
import scala.annotation.tailrec | |
import scala.util.Try | |
/** | |
* An abstraction for zip files and streams. Everything is written the way | |
* it is for performance: we come through here a lot on every run. Be careful | |
* about changing it. | |
* | |
* @author Philippe Altherr (original version) | |
* @author Paul Phillips (this one) | |
* @version 2.0, | |
* | |
* ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' | |
*/ | |
object ZipArchive { | |
/** | |
* @param file a File | |
* @return A ZipArchive if `file` is a readable zip file, otherwise null. | |
*/ | |
def fromFile(file: File): FileZipArchive = fromFile(file.jfile) | |
def fromFile(file: JFile): FileZipArchive = | |
try { new FileZipArchive(file) } | |
catch { case _: IOException => null } | |
/** | |
* @param url the url of a zip file | |
* @return A ZipArchive backed by the given url. | |
*/ | |
def fromURL(url: URL): URLZipArchive = new URLZipArchive(url) | |
def fromManifestURL(url: URL): AbstractFile = new ManifestResources(url) | |
private def dirName(path: String) = splitPath(path, front = true) | |
private def baseName(path: String) = splitPath(path, front = false) | |
private def splitPath(path0: String, front: Boolean): String = { | |
val isDir = path0.charAt(path0.length - 1) == '/' | |
val path = if (isDir) path0.substring(0, path0.length - 1) else path0 | |
val idx = path.lastIndexOf('/') | |
if (idx < 0) | |
if (front) "/" | |
else path | |
else if (front) path.substring(0, idx + 1) | |
else path.substring(idx + 1) | |
} | |
private val classmonkey: Option[Constructor[sun.misc.URLClassPath]] = Try(Class.forName("fommil.URLClassPath")).toOption.map { | |
klass => klass.getDeclaredConstructors.find(_.getGenericParameterTypes.length == 1).get.asInstanceOf[Constructor[sun.misc.URLClassPath]] | |
} | |
private[io] def ucp(url: URL): sun.misc.URLClassPath = classmonkey match { | |
case Some(monkey) => monkey.newInstance(Array(url)) | |
case None => | |
System.err.println("[scala.reflect.io.ZipArchive] scalac must run with the class-monkey agent") | |
throw new UnsupportedOperationException("must be running with class-monkey") | |
//new sun.misc.URLClassPath(Array(url)) | |
} | |
} | |
import ZipArchive._ | |
/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ | |
abstract class ZipArchive(override val file: JFile) extends AbstractFile with Equals { | |
self => | |
override def underlyingSource = Some(this) | |
def isDirectory = true | |
def lookupName(name: String, directory: Boolean) = unsupported() | |
def lookupNameUnchecked(name: String, directory: Boolean) = unsupported() | |
def create() = unsupported() | |
def delete() = unsupported() | |
def output = unsupported() | |
def container = unsupported() | |
def absolute = unsupported() | |
/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ | |
sealed abstract class Entry(path: String) extends VirtualFile(baseName(path), path) { | |
// have to keep this name for compat with sbt's compiler-interface | |
def getArchive: ZipFile = null | |
override def underlyingSource = Some(self) | |
override def toString = self.path + "(" + path + ")" | |
} | |
/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ | |
class DirEntry(path: String) extends Entry(path) { | |
val entries = mutable.HashMap[String, Entry]() | |
override def isDirectory = true | |
override def iterator: Iterator[Entry] = entries.valuesIterator | |
override def lookupName(name: String, directory: Boolean): Entry = { | |
if (directory) entries(name + "/") | |
else entries(name) | |
} | |
} | |
private def ensureDir(dirs: mutable.Map[String, DirEntry], path: String, zipEntry: ZipEntry): DirEntry = | |
//OPT inlined from getOrElseUpdate; saves ~50K closures on test run. | |
// was: | |
// dirs.getOrElseUpdate(path, { | |
// val parent = ensureDir(dirs, dirName(path), null) | |
// val dir = new DirEntry(path) | |
// parent.entries(baseName(path)) = dir | |
// dir | |
// }) | |
dirs get path match { | |
case Some(v) => v | |
case None => | |
val parent = ensureDir(dirs, dirName(path), null) | |
val dir = new DirEntry(path) | |
parent.entries(baseName(path)) = dir | |
dirs(path) = dir | |
dir | |
} | |
protected def getDir(dirs: mutable.Map[String, DirEntry], entry: ZipEntry): DirEntry = { | |
if (entry.isDirectory) ensureDir(dirs, entry.getName, entry) | |
else ensureDir(dirs, dirName(entry.getName), null) | |
} | |
} | |
/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ | |
final class FileZipArchive(file: JFile) extends ZipArchive(file) { | |
private class FileEntry( | |
time: Long, | |
contents: sun.misc.Resource | |
) extends Entry(contents.getName) { | |
override def getArchive: ZipFile = { | |
println("[error] ZipArchive.getArchive called, leaking files") | |
null | |
} | |
override def lastModified: Long = time | |
override def input: InputStream = contents.getInputStream | |
override def sizeOption: Option[Int] = Some(contents.getContentLength) | |
} | |
lazy val (root: DirEntry, allDirs: mutable.HashMap[String, DirEntry]) = { | |
val root = new DirEntry("/") | |
val dirs = mutable.HashMap[String, DirEntry]("/" -> root) | |
// NOTES on SI-9632 | |
// | |
// The original scalac implementation of this class opens the zip | |
// file and retains the file handle, relying on the GC to call the | |
// finalizers (which reliably doesn't happen). On UNIX-like OS the | |
// file handle will refer to the file at the point when it was | |
// created (causing a huge PermGen memory leak), on Windows it is | |
// impossible for any process to delete the file. | |
// | |
// The changes here are to aggressively load archives, storing | |
// in-memory, so that the file handle does not leak and ensuring | |
// that the jar looks identical to the original implementation | |
// even if it changes on disk (i.e. changes are ignored). | |
// | |
// If using class-monkey to load the contents of the jars, which | |
// is cached in-memory across the entire JVM (unless the jar | |
// changed since the last time getResource was called). If | |
// class-monkey is *not* used then the jar will be accessed for | |
// every read and will probably break in lots of ways. | |
// | |
// Note that a caveat of the scalac behaviour is that it assumes | |
// that jars do not change. | |
val zipFile = try { | |
new ZipFile(file) | |
} catch { | |
case ioe: IOException => throw new IOException("Error accessing " + file.getPath, ioe) | |
} | |
val loader = ZipArchive.ucp(file.toURI.toURL) | |
try { | |
val enum = zipFile.entries() | |
while (enum.hasMoreElements) { | |
val entry = enum.nextElement | |
val dir = getDir(dirs, entry) | |
if (entry.isDirectory) dir | |
else { | |
val name = entry.getName | |
if (name.endsWith(".class")) { | |
// only classes are needed | |
val time = entry.getTime | |
val contents = loader.getResource(name) | |
dir.entries(name) = new FileEntry(time, contents) | |
} | |
} | |
} | |
} finally { | |
Try(zipFile.close()) | |
Try(loader.closeLoaders()) | |
} | |
(root, dirs) | |
} | |
def iterator: Iterator[Entry] = root.iterator | |
def name = file.getName | |
def path = file.getPath | |
def input = File(file).inputStream() | |
def lastModified = file.lastModified | |
override def sizeOption = Some(file.length.toInt) | |
override def canEqual(other: Any) = other.isInstanceOf[FileZipArchive] | |
override def hashCode() = file.hashCode | |
override def equals(that: Any) = that match { | |
case x: FileZipArchive => file.getAbsoluteFile == x.file.getAbsoluteFile | |
case _ => false | |
} | |
} | |
/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ | |
final class URLZipArchive(val url: URL) extends ZipArchive(null) { | |
def iterator: Iterator[Entry] = { | |
val root = new DirEntry("/") | |
val dirs = mutable.HashMap[String, DirEntry]("/" -> root) | |
val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input))) | |
@tailrec def loop(): Unit = { | |
val zipEntry = in.getNextEntry() | |
class EmptyFileEntry() extends Entry(zipEntry.getName) { | |
override def toByteArray: Array[Byte] = null | |
override def sizeOption = Some(0) | |
} | |
class FileEntry() extends Entry(zipEntry.getName) { | |
override val toByteArray: Array[Byte] = { | |
val len = zipEntry.getSize().toInt | |
val arr = if (len == 0) Array.emptyByteArray else new Array[Byte](len) | |
var offset = 0 | |
def loop(): Unit = { | |
if (offset < len) { | |
val read = in.read(arr, offset, len - offset) | |
if (read >= 0) { | |
offset += read | |
loop() | |
} | |
} | |
} | |
loop() | |
if (offset == arr.length) arr | |
else throw new IOException("Input stream truncated: read %d of %d bytes".format(offset, len)) | |
} | |
override def sizeOption = Some(zipEntry.getSize().toInt) | |
} | |
if (zipEntry != null) { | |
val dir = getDir(dirs, zipEntry) | |
if (zipEntry.isDirectory) | |
dir | |
else { | |
val f = if (zipEntry.getSize() == 0) new EmptyFileEntry() else new FileEntry() | |
dir.entries(f.name) = f | |
} | |
in.closeEntry() | |
loop() | |
} | |
} | |
loop() | |
try root.iterator | |
finally dirs.clear() | |
} | |
def name = url.getFile() | |
def path = url.getPath() | |
def input = url.openStream() | |
def lastModified = | |
try url.openConnection().getLastModified() | |
catch { case _: IOException => 0 } | |
override def canEqual(other: Any) = other.isInstanceOf[URLZipArchive] | |
override def hashCode() = url.hashCode | |
override def equals(that: Any) = that match { | |
case x: URLZipArchive => url == x.url | |
case _ => false | |
} | |
} | |
final class ManifestResources(val url: URL) extends ZipArchive(null) { | |
def iterator = { | |
val root = new DirEntry("/") | |
val dirs = mutable.HashMap[String, DirEntry]("/" -> root) | |
val manifest = new Manifest(input) | |
val iter = manifest.getEntries().keySet().iterator().filter(_.endsWith(".class")).map(new ZipEntry(_)) | |
for (zipEntry <- iter) { | |
val dir = getDir(dirs, zipEntry) | |
if (!zipEntry.isDirectory) { | |
class FileEntry() extends Entry(zipEntry.getName) { | |
override def lastModified = zipEntry.getTime() | |
override def input = resourceInputStream(path) | |
override def sizeOption = None | |
} | |
val f = new FileEntry() | |
dir.entries(f.name) = f | |
} | |
} | |
try root.iterator | |
finally dirs.clear() | |
} | |
def name = path | |
def path: String = { | |
val s = url.getPath | |
val n = s.lastIndexOf('!') | |
s.substring(0, n) | |
} | |
def input = url.openStream() | |
def lastModified = | |
try url.openConnection().getLastModified() | |
catch { case _: IOException => 0 } | |
override def canEqual(other: Any) = other.isInstanceOf[ManifestResources] | |
override def hashCode() = url.hashCode | |
override def equals(that: Any) = that match { | |
case x: ManifestResources => url == x.url | |
case _ => false | |
} | |
private def resourceInputStream(path: String): InputStream = { | |
new FilterInputStream(null) { | |
override def read(): Int = { | |
if (in == null) in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path) | |
if (in == null) throw new RuntimeException(path + " not found") | |
super.read() | |
} | |
override def close(): Unit = { | |
super.close() | |
in = null | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment