diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/io/SourceReader.scala | 6 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/io/ZipArchive.scala | 193 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/util/ClassPath.scala | 45 |
3 files changed, 134 insertions, 110 deletions
diff --git a/src/compiler/scala/tools/nsc/io/SourceReader.scala b/src/compiler/scala/tools/nsc/io/SourceReader.scala index 37da2de83d..324c5e4111 100644 --- a/src/compiler/scala/tools/nsc/io/SourceReader.scala +++ b/src/compiler/scala/tools/nsc/io/SourceReader.scala @@ -49,9 +49,9 @@ class SourceReader(decoder: CharsetDecoder, reporter: Reporter) { */ def read(file: AbstractFile): Array[Char] = { try file match { - case p: PlainFile => read(p.file) - case z: ZipArchive#FileEntry => read(Channels.newChannel(z.input)) - case _ => read(ByteBuffer.wrap(file.toByteArray)) + case p: PlainFile => read(p.file) + case z: ZipArchive#Entry => read(Channels.newChannel(z.input)) + case _ => read(ByteBuffer.wrap(file.toByteArray)) } catch { case e: Exception => reportEncodingError("" + file) ; Array() diff --git a/src/compiler/scala/tools/nsc/io/ZipArchive.scala b/src/compiler/scala/tools/nsc/io/ZipArchive.scala index 4fd72ba006..6e4998738e 100644 --- a/src/compiler/scala/tools/nsc/io/ZipArchive.scala +++ b/src/compiler/scala/tools/nsc/io/ZipArchive.scala @@ -10,6 +10,7 @@ import java.net.URL import java.io.{ IOException, InputStream, ByteArrayInputStream } import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream } import scala.collection.{ immutable, mutable } +import annotation.tailrec /** 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 @@ -20,15 +21,15 @@ import scala.collection.{ immutable, mutable } * @version 2.0, */ object ZipArchive { - def fromPath(path: String): ZipArchive = fromFile(new JFile(path)) - 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) /** * @param file a File * @return A ZipArchive if `file` is a readable zip file, otherwise null. */ - def fromFile(file: File): ZipArchive = fromFile(file.jfile) - def fromFile(file: JFile): ZipArchive = + def fromFile(file: File): FileZipArchive = fromFile(file.jfile) + def fromFile(file: JFile): FileZipArchive = try { new FileZipArchive(file) } catch { case _: IOException => null } @@ -36,45 +37,54 @@ object ZipArchive { * @param url the url of a zip file * @return A ZipArchive backed by the given url. */ - def fromURL(url: URL): ZipArchive = new URLZipArchive(url) + 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._ abstract class ZipArchive(override val file: JFile) extends AbstractFile with Equals { self => - // The root of this archive, populated lazily. Implemented by File and URL. - def root: DirEntry - // The underlying zip file, or null if that's not what underlies it. - def zipFile: ZipFile - override def underlyingSource = Some(this) - def lookupName(name: String, directory: Boolean) = root.lookupName(name, directory) - def lookupNameUnchecked(name: String, directory: Boolean) = unsupported - def iterator = root.iterator 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 - // Accumulated directories in this zip. - // Violating my usual rule about vars holding mutable structures because - // I want to make abundantly certain it can be collected. - private var dirs: mutable.HashMap[String, DirEntry] = _ - protected def traverseAndClear(body: => Unit): DirEntry = { - dirs = mutable.HashMap[String, DirEntry]("/" -> new DirEntry("/")) - try { body ; dirs("/") } - finally { dirs.clear() ; dirs = null } + private def walkIterator(its: Iterator[AbstractFile]): Iterator[AbstractFile] = { + its flatMap { f => + if (f.isDirectory) walkIterator(f.iterator) + else Iterator(f) + } } + def deepIterator = walkIterator(iterator) - sealed abstract class Entry(override val container: DirEntry, path: String) extends VirtualFile(baseName(path), path) { - // have to keep this apparently for compat with sbt's compiler-interface - final def getArchive: ZipFile = self.zipFile - final def addToParent() = container.entries(name) = this + 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 + ")" + override def toString = self.path + "(" + path + ")" } - class DirEntry(override val path: String) extends Entry(getParent(path), path) { + class DirEntry(path: String) extends Entry(path) { val entries = mutable.HashMap[String, Entry]() override def isDirectory = true @@ -84,45 +94,21 @@ abstract class ZipArchive(override val file: JFile) extends AbstractFile with Eq else entries(name) } } - class FileEntry(zipEntry: ZipEntry) extends Entry(getDir(zipEntry), zipEntry.getName) { - lastModified = zipEntry.getTime() - - override def input = getArchive getInputStream zipEntry - override def sizeOption = Some(zipEntry.getSize().toInt) - } - - 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) - } - private def newDir(path: String, zipEntry: ZipEntry): DirEntry = { + private def newDir(dirs: mutable.Map[String, DirEntry], path: String, zipEntry: ZipEntry): DirEntry = { + val parent = { + val parentPath = dirName(path) + if (dirs contains parentPath) dirs(parentPath) + else newDir(dirs, parentPath, null) + } val dir = new DirEntry(path) if (zipEntry != null) dir.lastModified = zipEntry.getTime() - dir.addToParent() + parent.entries(baseName(path)) = dir dirs(path) = dir dir } - private def getParent(path: String): DirEntry = { - if (path == "/") null - else { - val parentPath = dirName(path) - if (dirs contains parentPath) dirs(parentPath) - else newDir(parentPath, null) - } - } - protected def getDir(entry: ZipEntry): DirEntry = { + protected def getDir(dirs: mutable.Map[String, DirEntry], entry: ZipEntry): DirEntry = { val name = entry.getName if (entry.isDirectory) { if (dirs contains name) { @@ -131,63 +117,104 @@ abstract class ZipArchive(override val file: JFile) extends AbstractFile with Eq existing.lastModified = entry.getTime() existing } - else newDir(name, entry) + else newDir(dirs, name, entry) } else { val path = dirName(name) if (dirs contains path) dirs(path) - else newDir(path, null) + else newDir(dirs, path, null) } } } final class FileZipArchive(file: JFile) extends ZipArchive(file) { - lazy val root = traverseAndClear(traverseZipFile(zipFile)) - def zipFile = new ZipFile(file) + def iterator = { + val zipFile = new ZipFile(file) + val root = new DirEntry("/") + val dirs = mutable.HashMap[String, DirEntry]("/" -> root) + val enum = zipFile.entries() - def traverseZipFile(z: ZipFile) { - val enum = z.entries() while (enum.hasMoreElements) { val zipEntry = enum.nextElement - if (zipEntry.isDirectory) getDir(zipEntry) - else new FileEntry(zipEntry).addToParent() + val dir = getDir(dirs, zipEntry) + if (zipEntry.isDirectory) dir + else { + class FileEntry() extends Entry(zipEntry.getName) { + override def getArchive = zipFile + override def input = getArchive getInputStream zipEntry + override def sizeOption = Some(zipEntry.getSize().toInt) + } + val f = new FileEntry() + f.lastModified = zipEntry.getTime() + dir.entries(f.name) = f + } } + + try root.iterator + finally dirs.clear() } - override def sizeOption = Some(file.length.toInt) def name = file.getName def path = file.getPath def input = File(file).inputStream() def lastModified = file.lastModified - def absolute = if (file.isAbsolute) this else new FileZipArchive(file.getAbsoluteFile) - def container = new PlainFile(file.getParent) + 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: ZipArchive => file.getAbsoluteFile == x.file.getAbsoluteFile - case _ => false + case x: FileZipArchive => file.getAbsoluteFile == x.file.getAbsoluteFile + case _ => false } } final class URLZipArchive(val url: URL) extends ZipArchive(null) { - lazy val root = traverseAndClear(traverseStream(input)) - def zipFile = null - - def traverseStream(input: InputStream) { - val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input))) + def iterator = { + val root = new DirEntry("/") + val dirs = mutable.HashMap[String, DirEntry]("/" -> root) + val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input))) - @annotation.tailrec def loop() { + @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() + + 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) { - if (zipEntry.isDirectory) getDir(zipEntry) - else new FileEntry(zipEntry).addToParent() + val dir = getDir(dirs, zipEntry) + if (zipEntry.isDirectory) + dir + else { + val f = new FileEntry() + dir.entries(f.name) = f + } in.closeEntry() loop() } } - try loop() - finally in.close() + + loop() + try root.iterator + finally dirs.clear() } def name = url.getFile() @@ -196,8 +223,6 @@ final class URLZipArchive(val url: URL) extends ZipArchive(null) { def lastModified = try url.openConnection().getLastModified() catch { case _: IOException => 0 } - def absolute = this - def container = unsupported override def canEqual(other: Any) = other.isInstanceOf[URLZipArchive] override def hashCode() = url.hashCode diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 25c09b6c15..2b3aa8696c 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -307,16 +307,19 @@ class SourcePath[T](dir: AbstractFile, val context: ClassPathContext[T]) extends def asClasspathString = dir.path val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq(dir) - lazy val classes: IndexedSeq[ClassRep] = dir flatMap { f => - if (f.isDirectory || !validSourceFile(f.name)) Nil - else List(ClassRep(None, Some(f))) - } toIndexedSeq - - lazy val packages: IndexedSeq[SourcePath[T]] = dir flatMap { f => - if (f.isDirectory && validPackage(f.name)) List(new SourcePath[T](f, context)) - else Nil - } toIndexedSeq + private def traverse() = { + val classBuf = immutable.Vector.newBuilder[ClassRep] + val packageBuf = immutable.Vector.newBuilder[SourcePath[T]] + dir foreach { f => + if (!f.isDirectory && validSourceFile(f.name)) + classBuf += ClassRep(None, Some(f)) + else if (f.isDirectory && validPackage(f.name)) + packageBuf += new SourcePath[T](f, context) + } + (packageBuf.result, classBuf.result) + } + lazy val (packages, classes) = traverse() override def toString() = "sourcepath: "+ dir.toString() } @@ -333,25 +336,21 @@ class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[Ab def asClasspathString = dir.path val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq() - lazy val classes: IndexedSeq[ClassRep] = { - val buf = immutable.Vector.newBuilder[ClassRep] + // calculates (packages, classes) in one traversal. + private def traverse() = { + val classBuf = immutable.Vector.newBuilder[ClassRep] + val packageBuf = immutable.Vector.newBuilder[DirectoryClassPath] dir foreach { f => if (!f.isDirectory && validClassFile(f.name)) - buf += ClassRep(Some(f), None) - } - buf.result - } - - lazy val packages: IndexedSeq[DirectoryClassPath] = { - val buf = immutable.Vector.newBuilder[DirectoryClassPath] - dir foreach { f => - if (f.isDirectory && validPackage(f.name)) - buf += new DirectoryClassPath(f, context) + classBuf += ClassRep(Some(f), None) + else if (f.isDirectory && validPackage(f.name)) + packageBuf += new DirectoryClassPath(f, context) } - buf.result + (packageBuf.result, classBuf.result) } - override def toString() = "directory classpath: "+ dir + lazy val (packages, classes) = traverse() + override def toString() = "directory classpath: "+ origin.getOrElse("?") } /** |