summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/io/ZipArchive.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/io/ZipArchive.scala')
-rw-r--r--src/compiler/scala/tools/nsc/io/ZipArchive.scala424
1 files changed, 161 insertions, 263 deletions
diff --git a/src/compiler/scala/tools/nsc/io/ZipArchive.scala b/src/compiler/scala/tools/nsc/io/ZipArchive.scala
index 22121cc714..90cb827280 100644
--- a/src/compiler/scala/tools/nsc/io/ZipArchive.scala
+++ b/src/compiler/scala/tools/nsc/io/ZipArchive.scala
@@ -1,317 +1,215 @@
/* NSC -- new Scala compiler
* Copyright 2005-2011 LAMP/EPFL
- * @author Martin Odersky
+ * @author Paul Phillips
*/
-
package scala.tools.nsc
package io
import java.net.URL
-import java.util.Enumeration
-import java.io.{ IOException, InputStream, BufferedInputStream, ByteArrayInputStream }
+import java.io.{ IOException, InputStream, ByteArrayInputStream }
import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream }
-import PartialFunction._
-
-import scala.collection.mutable.{ Map, HashMap }
+import scala.collection.{ immutable, mutable }
import annotation.tailrec
-/**
- * @author Philippe Altherr
- * @version 1.0, 23/03/2004
+/** 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,
*/
object ZipArchive {
- def fromPath(path: Path): ZipArchive = fromFile(path.toFile)
+ def fromPath(path: String): FileZipArchive = fromFile(new JFile(path))
+ def fromPath(path: Path): FileZipArchive = fromFile(path.toFile)
/**
- * If the specified file <code>file</code> exists and is a readable
- * zip archive, returns an abstract file backed by it. Otherwise,
- * returns <code>null</code>.
- *
- * @param file ...
- * @return ...
+ * @param file a File
+ * @return A ZipArchive if `file` is a readable zip file, otherwise null.
*/
- def fromFile(file: File): ZipArchive =
- try new ZipArchive(file, new ZipFile(file.jfile))
+ def fromFile(file: File): FileZipArchive = fromFile(file.jfile)
+ def fromFile(file: JFile): FileZipArchive =
+ try { new FileZipArchive(file) }
catch { case _: IOException => null }
/**
- * Returns an abstract directory backed by the specified archive.
- */
- def fromArchive(archive: ZipFile): ZipArchive =
- new ZipArchive(File(archive.getName()), archive)
-
- /**
- * Returns an abstract directory backed by the specified archive.
+ * @param url the url of a zip file
+ * @return A ZipArchive backed by the given url.
*/
- def fromURL(url: URL): AbstractFile = new URLZipArchive(url)
-
- private[io] trait ZipTrav extends Traversable[ZipEntry] {
- def zis: () => ZipInputStream
+ def fromURL(url: URL): URLZipArchive = new URLZipArchive(url)
+ def fromURL(url: String): URLZipArchive = fromURL(new URL(url))
+
+ private def dirName(path: String) = splitPath(path, true)
+ private def baseName(path: String) = splitPath(path, 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)
}
+}
+import ZipArchive._
- private[io] class ZipEntryTraversableClass(in: InputStream) extends ZipTrav {
- val zis = () => new ZipInputStream(in)
-
- def foreach[U](f: ZipEntry => U) = {
- var in: ZipInputStream = null
- @tailrec def loop(): Unit = {
- if (in.available == 0)
- return
+abstract class ZipArchive(override val file: JFile) extends AbstractFile with Equals {
+ self =>
- val entry = in.getNextEntry()
- if (entry != null) {
- f(entry)
- in.closeEntry()
- loop()
- }
- }
+ 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
- try {
- in = zis()
- loop()
- }
- finally in.close()
+ private def walkIterator(its: Iterator[AbstractFile]): Iterator[AbstractFile] = {
+ its flatMap { f =>
+ if (f.isDirectory) walkIterator(f.iterator)
+ else Iterator(f)
}
}
-}
-import ZipArchive.ZipTrav
-
-/** This abstraction aims to factor out the common code between
- * ZipArchive (backed by a zip file) and URLZipArchive (backed
- * by an InputStream.)
- */
-private[io] trait ZipContainer extends AbstractFile {
- /** Abstract types */
- type SourceType // InputStream or AbstractFile
- type CreationType // InputStream or ZipFile
-
- /** Abstract values */
- protected val creationSource: CreationType
- protected val root: DirEntryInterface
- protected def DirEntryConstructor: (AbstractFile, String, String) => DirEntryInterface
- protected def FileEntryConstructor: (SourceType, String, String, ZipEntry) => FileEntryInterface
- protected def ZipTravConstructor: CreationType => ZipTrav
+ def deepIterator = walkIterator(iterator)
- protected[io] trait EntryInterface extends VirtualFile {
- def name: String
- def path: String
+ 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 + ")"
}
-
- protected[io] trait DirEntryInterface extends EntryInterface {
- def source: SourceType
- val entries: Map[String, EntryInterface] = new HashMap()
- var entry: ZipEntry = _
-
- override def input = throw new Error("cannot read directories")
- override def lastModified: Long =
- if (entry ne null) entry.getTime() else super.lastModified
+ class DirEntry(path: String) extends Entry(path) {
+ val entries = mutable.HashMap[String, Entry]()
override def isDirectory = true
- override def iterator: Iterator[AbstractFile] = entries.valuesIterator
- override def lookupName(name: String, directory: Boolean): AbstractFile = {
- def slashName = if (directory) name + "/" else name
- entries.getOrElse(slashName, null)
+ override def iterator = entries.valuesIterator
+ override def lookupName(name: String, directory: Boolean): Entry = {
+ if (directory) entries(name + "/")
+ else entries(name)
}
}
- protected[io] trait FileEntryInterface extends EntryInterface {
- def entry: ZipEntry
-
- override def lastModified: Long = entry.getTime()
- override def sizeOption = Some(entry.getSize().toInt)
+ private def ensureDir(dirs: mutable.Map[String, DirEntry], path: String, zipEntry: ZipEntry): DirEntry = {
+ dirs.getOrElseUpdate(path, {
+ val parent = ensureDir(dirs, dirName(path), null)
+ val dir = new DirEntry(path)
+ parent.entries(baseName(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)
+ }
+}
- class ZipRootCreator(f: ZipRootCreator => SourceType) {
- val root = DirEntryConstructor(ZipContainer.this, "<root>", "/")
-
- // Map from paths to DirEntries
- val dirs = HashMap[String, DirEntryInterface]("/" -> root)
- val traverser = ZipTravConstructor(creationSource)
- private[this] var _parent: DirEntryInterface = _
- def parent = _parent
-
- def addEntry(entry: ZipEntry) {
- val path = entry.getName
- if (entry.isDirectory) {
- val dir: DirEntryInterface = getDir(dirs, path)
- if (dir.entry == null) dir.entry = entry
- }
+final class FileZipArchive(file: JFile) extends ZipArchive(file) {
+ def iterator = {
+ val zipFile = new ZipFile(file)
+ val root = new DirEntry("/")
+ val dirs = mutable.HashMap[String, DirEntry]("/" -> root)
+ val enum = zipFile.entries()
+
+ while (enum.hasMoreElements) {
+ val zipEntry = enum.nextElement
+ val dir = getDir(dirs, zipEntry)
+ if (zipEntry.isDirectory) dir
else {
- val (home, name) = splitPath(path)
- _parent = getDir(dirs, home)
- _parent.entries(name) = FileEntryConstructor(f(this), name, path, entry)
+ class FileEntry() extends Entry(zipEntry.getName) {
+ override def getArchive = zipFile
+ override def lastModified = zipEntry.getTime()
+ override def input = getArchive getInputStream zipEntry
+ override def sizeOption = Some(zipEntry.getSize().toInt)
+ }
+ val f = new FileEntry()
+ dir.entries(f.name) = f
}
}
- def apply() = {
- traverser foreach addEntry
- root
- }
+ try root.iterator
+ finally dirs.clear()
}
- protected def splitPath(path: String): (String, String) = {
- (path lastIndexOf '/') match {
- case -1 => ("/", path)
- case idx => path splitAt (idx + 1)
- }
+ 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
}
-
- /**
- * Returns the abstract file in this abstract directory with the
- * specified name. If there is no such file, returns null. The
- * argument "directory" tells whether to look for a directory or
- * or a regular file.
- */
- override def lookupName(name: String, directory: Boolean): AbstractFile =
- root.lookupName(name, directory)
-
- /** Returns an abstract file with the given name. It does not
- * check that it exists.
- */
- override def lookupNameUnchecked(name: String, directory: Boolean) = unsupported
-
- /** Returns all abstract subfiles of this abstract directory. */
- override def iterator: Iterator[AbstractFile] = root.iterator
-
- /**
- * Looks up the path in the given map and returns if found.
- * If not present, creates a new DirEntry, adds to both given
- * map and parent.entries, and returns it.
- */
- protected def getDir(dirs: Map[String, DirEntryInterface], path: String): DirEntryInterface =
- dirs.getOrElseUpdate(path, {
- val (home, name) = splitPath(path init)
- val parent = getDir(dirs, home)
- val dir = DirEntryConstructor(parent, name, path)
- parent.entries(name + path.last) = dir
- dir
- })
-
- override def isDirectory = true
}
-/**
- * This class implements an abstract directory backed by a zip
- * archive. We let the encoding be <code>null</code>, because we behave like
- * a directory.
- *
- * @author Philippe Altherr
- * @version 1.0, 23/03/2004
- */
-final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) with ZipContainer {
- self =>
-
- type SourceType = AbstractFile
- type CreationType = ZipFile
-
- protected val creationSource = archive
- protected lazy val root = new ZipRootCreator(_.parent)()
- protected def DirEntryConstructor = new DirEntry(_, _, _)
- protected def FileEntryConstructor = new FileEntry(_, _, _, _)
- protected def ZipTravConstructor = new ZipFileIterable(_)
-
- abstract class Entry(
- override val container: AbstractFile,
- name: String,
- path: String
- ) extends VirtualFile(name, path)
- {
- override def underlyingSource = Some(self)
- final override def path = "%s(%s)".format(self, super.path)
- final def archive = self.archive
-
- override def hashCode = super.hashCode + container.hashCode
- override def equals(that : Any) =
- super.equals(that) && (cond(that) {
- case e: Entry => container == e.container
- })
- }
-
- final class DirEntry(
- container: AbstractFile,
- name: String,
- path: String
- ) extends Entry(container, name, path) with DirEntryInterface
- {
- def source = container
- }
+final class URLZipArchive(val url: URL) extends ZipArchive(null) {
+ def iterator = {
+ val root = new DirEntry("/")
+ val dirs = mutable.HashMap[String, DirEntry]("/" -> root)
+ val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input)))
+
+ @tailrec def loop() {
+ val zipEntry = in.getNextEntry()
+ class FileEntry() extends Entry(zipEntry.getName) {
+ override val toByteArray: Array[Byte] = {
+ val len = zipEntry.getSize().toInt
+ val arr = new Array[Byte](len)
+ var offset = 0
+
+ def loop() {
+ if (offset < len) {
+ val read = in.read(arr, offset, len - offset)
+ if (read >= 0) {
+ offset += read
+ loop()
+ }
+ }
+ }
+ loop()
- final class FileEntry(
- container: AbstractFile,
- name: String,
- path: String,
- val entry: ZipEntry
- ) extends Entry(container, name, path) with FileEntryInterface
- {
- override def input = archive getInputStream entry
- }
+ 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)
+ }
- class ZipFileIterable(z: ZipFile) extends Iterable[ZipEntry] with ZipTrav {
- def zis: () => ZipInputStream = null // not valid for this type
- def iterator = new Iterator[ZipEntry] {
- val enum = z.entries()
- def hasNext = enum.hasMoreElements
- def next = enum.nextElement
+ if (zipEntry != null) {
+ val dir = getDir(dirs, zipEntry)
+ if (zipEntry.isDirectory)
+ dir
+ else {
+ val f = new FileEntry()
+ dir.entries(f.name) = f
+ }
+ in.closeEntry()
+ loop()
+ }
}
- }
-}
-/**
- * This class implements an abstract directory backed by a specified
- * zip archive.
- *
- * @author Stephane Micheloud
- * @version 1.0, 29/05/2007
- */
-final class URLZipArchive(url: URL) extends AbstractFile with ZipContainer {
- type SourceType = InputStream
- type CreationType = InputStream
-
- protected lazy val creationSource = input
- protected lazy val root = new ZipRootCreator(x => byteInputStream(x.traverser.zis()))()
-
- protected def DirEntryConstructor = (_, name, path) => new DirEntry(name, path)
- protected def FileEntryConstructor = new FileEntry(_, _, _, _)
- protected def ZipTravConstructor = new ZipArchive.ZipEntryTraversableClass(_)
+ loop()
+ try root.iterator
+ finally dirs.clear()
+ }
- def name: String = url.getFile()
- def path: String = url.getPath()
- def input: InputStream = url.openStream()
- def absolute: AbstractFile = this
- def lastModified: Long =
+ def name = url.getFile()
+ def path = url.getPath()
+ def input = url.openStream()
+ def lastModified =
try url.openConnection().getLastModified()
catch { case _: IOException => 0 }
- /** Methods we don't support but have to implement because of the design */
- def file: JFile = null
- def create(): Unit = unsupported
- def delete(): Unit = unsupported
- def output = unsupported
- def container = unsupported
-
- abstract class Entry(name: String, path: String) extends VirtualFile(name, path) {
- final override def path = "%s(%s)".format(URLZipArchive.this, super.path)
- override def container = URLZipArchive.this
- }
- final class DirEntry(name: String, path: String) extends Entry(name, path) with DirEntryInterface {
- def source = input
- }
- final class FileEntry(
- val in: InputStream,
- name: String,
- path: String,
- val entry: ZipEntry
- ) extends Entry(name, path) with FileEntryInterface
- {
- override def input = in
- }
-
- /** Private methods **/
- private def byteInputStream(in: InputStream): InputStream = {
- val minusOne = (-1).toByte
- val buf = new BufferedInputStream(in)
- val bytes = Iterator continually in.read().toByte takeWhile (_ != minusOne)
- new ByteArrayInputStream(bytes.toSeq.toArray)
+ 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
}
}