diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/io/ZipArchive.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/io/ZipArchive.scala | 424 |
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 } } |