summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/io/ZipArchive.scala
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2009-09-07 22:16:58 +0000
committerPaul Phillips <paulp@improving.org>2009-09-07 22:16:58 +0000
commitaef123719dbe81b20fe3eed6390df9d62fd24a0b (patch)
tree0148ea23a2d709ac7173edf3e87122bb9d580eb3 /src/compiler/scala/tools/nsc/io/ZipArchive.scala
parent3335e037a83edef65eb3c6fa75be23c0d0a46aab (diff)
downloadscala-aef123719dbe81b20fe3eed6390df9d62fd24a0b.tar.gz
scala-aef123719dbe81b20fe3eed6390df9d62fd24a0b.tar.bz2
scala-aef123719dbe81b20fe3eed6390df9d62fd24a0b.zip
Reverts scala.io.* to its natural state, and th...
Reverts scala.io.* to its natural state, and the rest of trunk to using java.io.File. Anyone who wants to salvage any usable bits is of course welcome to do so, so long as they also assume responsibility for those bits.
Diffstat (limited to 'src/compiler/scala/tools/nsc/io/ZipArchive.scala')
-rw-r--r--src/compiler/scala/tools/nsc/io/ZipArchive.scala479
1 files changed, 270 insertions, 209 deletions
diff --git a/src/compiler/scala/tools/nsc/io/ZipArchive.scala b/src/compiler/scala/tools/nsc/io/ZipArchive.scala
index 2221a3df03..9d322af889 100644
--- a/src/compiler/scala/tools/nsc/io/ZipArchive.scala
+++ b/src/compiler/scala/tools/nsc/io/ZipArchive.scala
@@ -8,25 +8,28 @@
package scala.tools.nsc
package io
-import scala.io.{ File, Path }
+import java.io.{File, IOException, InputStream}
import java.net.URL
import java.util.Enumeration
-import java.io.{ File => JFile, IOException, InputStream, BufferedInputStream, ByteArrayInputStream }
-import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream }
-import PartialFunction._
+import java.util.zip.{ZipEntry, ZipFile, ZipInputStream}
-import scala.collection.Traversable
-import scala.collection.mutable.{ Map, HashMap }
-import scala.collection.immutable.{ StringVector => SV }
-import scala.collection.JavaConversions.asIterator
+import scala.collection.mutable.{Map, HashMap}
/**
* @author Philippe Altherr
* @version 1.0, 23/03/2004
*/
-object ZipArchive
-{
- def fromPath(path: Path): ZipArchive = fromFile(path.toFile)
+object ZipArchive {
+
+ //########################################################################
+
+ /**
+ * ...
+ *
+ * @param path ...
+ * @return ...
+ */
+ def fromPath(path: String): AbstractFile = fromFile(new File(path))
/**
* If the specified file <code>file</code> exists and is a readable
@@ -36,115 +39,56 @@ object ZipArchive
* @param file ...
* @return ...
*/
- def fromFile(file: File): ZipArchive =
- try new ZipArchive(file, new ZipFile(file.jfile))
+ def fromFile(file: File): AbstractFile =
+ try { new ZipArchive(file, new ZipFile(file)) }
catch { case _: IOException => null }
/**
* Returns an abstract directory backed by the specified archive.
+ *
+ * @param archive ...
+ * @return ...
*/
- def fromArchive(archive: ZipFile): ZipArchive =
- new ZipArchive(File(archive.getName()), archive)
+ def fromArchive(archive: ZipFile): AbstractFile =
+ new ZipArchive(new File(archive.getName()), archive)
/**
* Returns an abstract directory backed by the specified archive.
+ *
+ * @param url ...
+ * @return ...
*/
- def fromURL(url: URL): AbstractFile = new URLZipArchive(url)
-
- private[io] class ZipEntryTraversableClass(in: InputStream) extends Traversable[ZipEntry] {
- val zis = new ZipInputStream(in)
-
- def foreach[U](f: ZipEntry => U) = {
- def loop(x: ZipEntry): Unit = if (x != null) {
- f(x)
- zis.closeEntry()
- loop(zis.getNextEntry())
- }
- loop(zis.getNextEntry())
- }
- }
+ def fromURL(url: URL): AbstractFile =
+ new URLZipArchive(url)
}
-/** This abstraction aims to factor out the common code between
- * ZipArchive (backed by a zip file) and URLZipArchive (backed
- * by an InputStream.)
+/**
+ * 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
*/
-private[io] trait ZipContainer extends AbstractFile
-{
- /** Abstract types */
- type SourceType // InputStream or AbstractFile
- type CreationType // InputStream or ZipFile
- type ZipTrav = Traversable[ZipEntry] { def zis: ZipInputStream }
-
- /** 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
-
- protected[io] trait EntryInterface extends VirtualFile {
- def name: String
- def path: String
- }
-
- 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
+final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) {
- 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)
- }
- }
+ assert(archive ne null)
+ //########################################################################
+ // Private Fields
- protected[io] trait FileEntryInterface extends EntryInterface {
- def entry: ZipEntry
+ /** The root directory or null if not yet initialized */
+ private var root: DirEntry = _
- override def lastModified: Long = entry.getTime()
- override def sizeOption = Some(entry.getSize().toInt)
- }
+ //########################################################################
+ // Public Methods
- 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
- }
- else {
- val (home, name) = splitPath(path)
- _parent = getDir(dirs, home)
- _parent.entries(name) = FileEntryConstructor(f(this), name, path, entry)
- }
- }
-
- def apply() = {
- traverser foreach addEntry
- root
- }
- }
+ /** Returns true. */
+ override def isDirectory = true
- protected def splitPath(path: String): (String, String) = {
- (path lastIndexOf '/') match {
- case -1 => ("/", path)
- case idx => SV.splitAt(path, idx + 1)
- }
+ /** Returns all abstract subfiles of this abstract directory. */
+ override def iterator: Iterator[AbstractFile] = {
+ if (root eq null) load()
+ root.iterator
}
/**
@@ -153,8 +97,10 @@ private[io] trait ZipContainer extends AbstractFile
* argument "directory" tells whether to look for a directory or
* or a regular file.
*/
- override def lookupName(name: String, directory: Boolean): AbstractFile =
+ override def lookupName(name: String, directory: Boolean): AbstractFile = {
+ if (root eq null) load()
root.lookupName(name, directory)
+ }
/** Returns an abstract file with the given name. It does not
* check that it exists.
@@ -162,90 +108,114 @@ private[io] trait ZipContainer extends AbstractFile
override def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile =
throw new UnsupportedOperationException()
- /** Returns all abstract subfiles of this abstract directory. */
- override def iterator: Iterator[AbstractFile] = root.iterator
+ //########################################################################
+ // Private Methods
+
+ /** Loads the archive and creates the root directory. */
+ private def load() {
+ this.root = new DirEntry(this, "<root>", "/")
+ // A path to DirEntry map
+ val dirs: Map[String, DirEntry] = new HashMap()
+ dirs.update("/", root)
+ val entries = archive.entries()
+ while (entries.hasMoreElements()) {
+ val entry = entries.nextElement().asInstanceOf[ZipEntry]
+ val path = entry.getName()
+ assert(entry.isDirectory() == path.endsWith("/"),
+ this.toString() + " - " + path);
+ if (entry.isDirectory()) {
+ val dir: DirEntry = getDir(dirs, path)
+ // this assertion causes an unnecessary bomb if a directory is twice listed in the jar
+ // assert(dir.entry eq null, this.toString() + " - " + path)
+ if (dir.entry eq null) dir.entry = entry
+ } else {
+ val index = path.lastIndexOf('/')
+ val name = if (index < 0) path else path.substring(index + 1)
+ val home = if (index < 0) "/" else path.substring(0, index + 1)
+ val parent: DirEntry = getDir(dirs, home)
+ // OLD: assert(!parent.entries.contains(path))
+ // MAYBE: assert(!parent.entries.contains(name))
+ //if (parent.entries.contains(name))
+ // Console.println("XXX: " + this.toString() + " - " + home + "/" + name)
+ parent.entries.update(name, new FileEntry(parent, name, path, entry))
+ }
+ }
+ }
/**
- * 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.
+ * Lookups the specified table for a DirEntry with the specified
+ * path. If successful, returns the found DirEntry. Otherwise
+ * creates a new DirEntry, enters it into the table and in the
+ * table of its parent ZipDir and returns it.
*/
- protected def getDir(dirs: Map[String, DirEntryInterface], path: String): DirEntryInterface =
- dirs.getOrElseUpdate(path, {
- val (home, name) = splitPath(SV init path)
- val parent = getDir(dirs, home)
- val dir = DirEntryConstructor(parent, name, path)
- parent.entries(name + path.last) = dir
- dir
+ private def getDir(dirs: Map[String,DirEntry], path: String): DirEntry =
+ dirs.get(path) match {
+ case Some(dir) => dir
+ case None =>
+ val index = path.lastIndexOf('/', path.length() - 2);
+ val name = if (index < 0) path else path.substring(index + 1);
+ val home = if (index < 0) "/" else path.substring(0, index + 1);
+ val parent: DirEntry = getDir(dirs, home);
+ val dir = new DirEntry(parent, name.substring(0, name.length() - 1), path);
+ parent.entries.update(name, dir);
+ dirs.update(path, dir);
+ dir
+ }
+
+ //########################################################################
+ // Private Class - Entry
+
+ /** Superclass of archive entries */
+ abstract class Entry(override val container : AbstractFile, name: String, path: String)
+ extends VirtualFile(name, path) {
+ final override def path = ZipArchive.this.toString() + "(" + pathInArchive + ")"
+ final def getArchive = ZipArchive.this.archive
+ def pathInArchive = super.path
+ override def hashCode = super.hashCode + container.hashCode
+ override def equals(that : Any) = super.equals(that) && (that match {
+ case entry : Entry => container == entry.container
+ case _ => false
})
+ }
- override def isDirectory = true
-}
+ //########################################################################
+ // Private Class - DirEntry
-/**
- * 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 = zipTraversableFromZipFile _
-
- abstract class Entry(
- override val container: AbstractFile,
- name: String,
- path: String
- ) extends VirtualFile(name, path)
+ /** A directory archive entry */
+ private final class DirEntry(container : AbstractFile, name: String, path: String)
+ extends Entry(container, name, path)
{
- final override def path = "%s(%s)".format(self, pathInArchive)
- final def getArchive = self.archive
- def pathInArchive = super.path
- override def hashCode = super.hashCode + container.hashCode
- override def equals(that : Any) =
- super.equals(that) && (cond(that) {
- case e: Entry => container == e.container
- })
- }
+ val entries: Map[String, Entry] = new HashMap()
- final class DirEntry(
- container: AbstractFile,
- name: String,
- path: String
- ) extends Entry(container, name, path) with DirEntryInterface
- {
- def source = container
- }
+ var entry: ZipEntry = _
- final class FileEntry(
- container: AbstractFile,
- name: String,
- path: String,
- val entry: ZipEntry
- ) extends Entry(container, name, path) with FileEntryInterface
- {
- def archive = self.archive
- override def input = archive getInputStream entry
- }
+ override def isDirectory = true
+ override def input = throw new Error("cannot read directories")
- private def zipTraversableFromZipFile(z: ZipFile): ZipTrav =
- new Traversable[ZipEntry] {
- def zis: ZipInputStream = null // not valid for this type
- val itStream = asIterator(z.entries()).toStream
- def foreach[U](f: ZipEntry => U) = itStream foreach f
+ override def lastModified: Long =
+ if (entry ne null) entry.getTime() else super.lastModified
+
+ override def iterator: Iterator[AbstractFile] = entries.valuesIterator
+
+ override def lookupName(name: String, directory: Boolean): AbstractFile =
+ entries.get(if (directory) name + "/" else name) match {
+ case Some(dir) => dir
+ case None => null
}
+ }
+
+ //########################################################################
+ // Private Class - FileEntry
+
+ /** A regular file archive entry */
+ final class FileEntry(container : AbstractFile, name: String, path: String, val entry: ZipEntry)
+ extends Entry(container, name, path) {
+ def archive = ZipArchive.this.archive
+ override def lastModified: Long = entry.getTime()
+ override def input = archive.getInputStream(entry)
+ override def sizeOption = Some(entry.getSize().toInt)
+ }
}
/**
@@ -255,53 +225,144 @@ final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file)
* @author Stephane Micheloud
* @version 1.0, 29/05/2007
*/
-final class URLZipArchive(url: URL) extends AbstractFile with ZipContainer
-{
- type SourceType = InputStream
- type CreationType = InputStream
+final class URLZipArchive(url: URL) extends AbstractFile {
+ assert(url ne null)
- protected lazy val creationSource = input
- protected lazy val root = new ZipRootCreator(x => byteInputStream(x.traverser.zis))()
+ private var root: DirEntry = _
- protected def DirEntryConstructor = (_, name, path) => new DirEntry(name, path)
- protected def FileEntryConstructor = new FileEntry(_, _, _, _)
- protected def ZipTravConstructor = new ZipArchive.ZipEntryTraversableClass(_)
+ def container = throw new Error("unsupported")
def name: String = url.getFile()
+
def path: String = url.getPath()
- def input: InputStream = url.openStream()
+
+ def file: File = null
+
def absolute: AbstractFile = this
+
+ def isDirectory: Boolean = true
+
def lastModified: Long =
- try url.openConnection().getLastModified()
- catch { case _: IOException => 0 }
+ try { url.openConnection().getLastModified() }
+ catch { case _ => 0 }
+
+ /** Does this abstract file denote an existing file? */
+ def create {
+ throw new UnsupportedOperationException
+ }
+
+ /** Delete the underlying file or directory (recursively). */
+ def delete {
+ throw new UnsupportedOperationException
+ }
+
+ def input: InputStream = url.openStream()
- /** Methods we don't support but have to implement because of the design */
- def file: JFile = null
- def create: Unit = throw new UnsupportedOperationException
- def delete: Unit = throw new UnsupportedOperationException
def output = throw new Error("unsupported")
- def container = throw new Error("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 iterator: Iterator[AbstractFile] = {
+ if (root eq null) load()
+ root.iterator
}
- final class DirEntry(name: String, path: String) extends Entry(name, path) with DirEntryInterface {
- def source = input
+
+ override def lookupName(name: String, directory: Boolean): AbstractFile = {
+ if (root eq null) load()
+ root.lookupName(name, directory)
+ }
+
+ /** Returns an abstract file with the given name. It does not
+ * check that it exists.
+ */
+ def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile =
+ throw new UnsupportedOperationException()
+
+ private def load() {
+ def getEntryInputStream(in: InputStream): InputStream = {
+ val buf = new scala.collection.mutable.ArrayBuffer[Byte]
+ val data = new Array[Byte](1024)
+ var n = in.read(data)
+ while (n > 0) {
+ buf.++=(data, 0, n)
+ n = in.read(data)
+ }
+ new java.io.ByteArrayInputStream(buf.toArray)
+ }
+ this.root = new DirEntry("<root>", "/")
+ // A path to DirEntry map
+ val dirs: Map[String, DirEntry] = new HashMap()
+ dirs.update("/", root)
+ val zis = new ZipInputStream(input)
+ var entry = zis.getNextEntry()
+ while (entry ne null) {
+ val path = entry.getName()
+ assert(entry.isDirectory() == path.endsWith("/"),
+ this.toString() + " - " + path);
+ if (entry.isDirectory()) {
+ val dir: DirEntry = getDir(dirs, path)
+ assert(dir.entry eq null, this.toString() + " - " + path)
+ dir.entry = entry
+ } else {
+ val index = path.lastIndexOf('/')
+ val name = if (index < 0) path else path.substring(index + 1)
+ val home = if (index < 0) "/" else path.substring(0, index + 1)
+ val parent: DirEntry = getDir(dirs, home)
+ assert(!parent.entries.contains(path), this.toString() + " - " + path)
+ val in = getEntryInputStream(zis)
+ parent.entries.update(name, new FileEntry(name, path, entry, in))
+ }
+ zis.closeEntry()
+ entry = zis.getNextEntry()
+ }
+ }
+
+ private def getDir(dirs: Map[String, DirEntry], path: String): DirEntry =
+ dirs.get(path) match {
+ case Some(dir) => dir
+ case None =>
+ val index = path.lastIndexOf('/', path.length() - 2)
+ val name = if (index < 0) path else path.substring(index + 1)
+ val home = if (index < 0) "/" else path.substring(0, index + 1)
+ val parent: DirEntry = getDir(dirs, home)
+ val dir = new DirEntry(name.substring(0, name.length() - 1), path)
+ parent.entries.update(name, dir)
+ dirs.update(path, dir)
+ dir
+ }
+
+ /** Superclass of archive entries */
+ abstract class Entry(name: String, path: String)
+ extends VirtualFile(name, path) {
+ final override def path = URLZipArchive.this.toString() + "(" + super.path + ")"
+ //final def getArchive = URLZipArchive.this.archive
}
- final class FileEntry(
- val in: InputStream,
- name: String,
- path: String,
- val entry: ZipEntry
- ) extends Entry(name, path) with FileEntryInterface
+
+ /** A directory archive entry */
+ private final class DirEntry(name: String, path: String)
+ extends Entry(name, path)
{
- override def input = in
+ val entries: Map[String, Entry] = new HashMap()
+ var entry: ZipEntry = _
+
+ override def isDirectory = true
+ override def input = throw new Error("cannot read directories")
+
+ override def lastModified: Long =
+ if (entry ne null) entry.getTime() else super.lastModified
+
+ override def iterator: Iterator[AbstractFile] = entries.valuesIterator
+
+ override def lookupName(name: String, directory: Boolean): AbstractFile =
+ entries.get(if (directory) name + "/" else name) match {
+ case Some(dir) => dir
+ case None => null
+ }
}
- /** Private methods **/
- private def byteInputStream(in: InputStream): InputStream = {
- val buf = new BufferedInputStream(in)
- val bytes = Iterator continually in.read().toByte takeWhile (_ != -1)
- new ByteArrayInputStream(bytes.toSequence.toArray)
+ final class FileEntry(name: String, path: String,
+ val entry: ZipEntry, val in: InputStream)
+ extends Entry(name, path) {
+ override def lastModified: Long = entry.getTime()
+ override def input = in
+ override def sizeOption = Some(entry.getSize().toInt)
}
}