From 2bd6b4ae4010b3c65cb36d876c2ca94cb6d48b14 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 12 May 2011 05:34:37 +0000 Subject: Fairly ruthlessly optimized ZipArchive. results from the profiler, but it sure isn't slower and it shed 125 lines or so. No review. --- .../scala/tools/nsc/backend/JavaPlatform.scala | 17 +- .../scala/tools/nsc/interpreter/IMain.scala | 2 +- src/compiler/scala/tools/nsc/io/SourceReader.scala | 50 +-- src/compiler/scala/tools/nsc/io/ZipArchive.scala | 385 +++++++-------------- src/compiler/scala/tools/nsc/util/ClassPath.scala | 4 +- src/compiler/scala/tools/util/PathResolver.scala | 16 +- 6 files changed, 160 insertions(+), 314 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 1891c8f13d..522b1ddd39 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -13,10 +13,10 @@ import scala.tools.util.PathResolver trait JavaPlatform extends Platform[AbstractFile] { import global._ - import definitions.{ BoxesRunTimeClass, getMember } + import definitions._ lazy val classPath = new PathResolver(settings).result - def rootLoader = new loaders.JavaPackageLoader(classPath) + def rootLoader = new loaders.JavaPackageLoader(classPath) private def depAnalysisPhase = if (settings.make.isDefault) Nil @@ -26,20 +26,19 @@ trait JavaPlatform extends Platform[AbstractFile] { flatten, // get rid of inner classes liftcode, // generate reified trees genJVM // generate .class files - ) ::: depAnalysisPhase + ) ++ depAnalysisPhase - lazy val externalEquals = getMember(BoxesRunTimeClass, nme.equals_) - def externalEqualsNumNum = getMember(BoxesRunTimeClass, "equalsNumNum") - def externalEqualsNumChar = getMember(BoxesRunTimeClass, "equalsNumChar") - def externalEqualsNumObject = getMember(BoxesRunTimeClass, "equalsNumObject") + lazy val externalEquals = getMember(BoxesRunTimeClass, nme.equals_) + lazy val externalEqualsNumNum = getMember(BoxesRunTimeClass, "equalsNumNum") + lazy val externalEqualsNumChar = getMember(BoxesRunTimeClass, "equalsNumChar") + lazy val externalEqualsNumObject = getMember(BoxesRunTimeClass, "equalsNumObject") /** We could get away with excluding BoxedBooleanClass for the * purpose of equality testing since it need not compare equal * to anything but other booleans, but it should be present in * case this is put to other uses. */ - def isMaybeBoxed(sym: Symbol): Boolean = { - import definitions._ + def isMaybeBoxed(sym: Symbol) = { (sym == ObjectClass) || (sym == JavaSerializableClass) || (sym == ComparableClass) || diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 8f6cd049bd..69858e8bba 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -244,7 +244,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo } /** the compiler's classpath, as URL's */ - lazy val compilerClasspath: List[URL] = new PathResolver(settings) asURLs + lazy val compilerClasspath = global.classPath.asURLs /* A single class loader is used for all commands interpreted by this Interpreter. It would also be possible to create a new class loader for each command diff --git a/src/compiler/scala/tools/nsc/io/SourceReader.scala b/src/compiler/scala/tools/nsc/io/SourceReader.scala index 7c9b776eb3..37da2de83d 100644 --- a/src/compiler/scala/tools/nsc/io/SourceReader.scala +++ b/src/compiler/scala/tools/nsc/io/SourceReader.scala @@ -33,58 +33,38 @@ class SourceReader(decoder: CharsetDecoder, reporter: Reporter) { "Please try specifying another one using the -encoding option") } - //######################################################################## - // Public Methods - /** Reads the file with the specified name. */ def read(filename: String): Array[Char]= read(new JFile(filename)) /** Reads the specified file. */ def read(file: JFile): Array[Char] = { val c = new FileInputStream(file).getChannel - try { - read(c) - } catch { - case e:Exception => - if (true) e.printStackTrace - reportEncodingError(file.toString()) - new Array[Char](0) - } finally { - c.close() - } + + try read(c) + catch { case e: Exception => reportEncodingError("" + file) ; Array() } + finally c.close() } /** Reads the specified file. - * - * @param file ... - * @return ... */ def read(file: AbstractFile): Array[Char] = { - file match { - case p:PlainFile => - read(p.file) // bq: (!!!) - case z:ZipArchive#FileEntry => - val c = Channels.newChannel(z.archive.getInputStream(z.entry)) - read(c) - case _ => - val b = ByteBuffer.wrap(file.toByteArray) - try { - read(b) - } catch { - case e:Exception => - if (true) e.printStackTrace - reportEncodingError(file.toString()) - new Array[Char](0) - } + try file match { + case p: PlainFile => read(p.file) + case z: ZipArchive#FileEntry => read(Channels.newChannel(z.input)) + case _ => read(ByteBuffer.wrap(file.toByteArray)) + } + catch { + case e: Exception => reportEncodingError("" + file) ; Array() } } /** Reads the specified byte channel. */ protected def read(input: ReadableByteChannel): Array[Char] = { val decoder: CharsetDecoder = this.decoder.reset() - val bytes: ByteBuffer = this.bytes; bytes.clear() - var chars: CharBuffer = this.chars; chars.clear() - var endOfInput: Boolean = false + val bytes: ByteBuffer = this.bytes; bytes.clear() + var chars: CharBuffer = this.chars; chars.clear() + var endOfInput = false + while (!endOfInput ) { endOfInput = input.read(bytes) < 0 bytes.flip() diff --git a/src/compiler/scala/tools/nsc/io/ZipArchive.scala b/src/compiler/scala/tools/nsc/io/ZipArchive.scala index 52d6c41354..2de0ac8285 100644 --- a/src/compiler/scala/tools/nsc/io/ZipArchive.scala +++ b/src/compiler/scala/tools/nsc/io/ZipArchive.scala @@ -1,163 +1,106 @@ /* 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 annotation.tailrec +import scala.collection.{ immutable, mutable } -/** - * @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) /** - * If the specified file file exists and is a readable - * zip archive, returns an abstract file backed by it. Otherwise, - * returns null. - * - * @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)) + try new FileZipArchive(file, new ZipFile(file.jfile)) catch { case _: IOException => null } /** - * Returns an abstract directory backed by the specified archive. + * @param zipFile a ZipFile + * @return A ZipArchive if `zipFile` is a readable zip file, otherwise null. */ - def fromArchive(archive: ZipFile): ZipArchive = - new ZipArchive(File(archive.getName()), archive) + def fromArchive(zipFile: ZipFile): ZipArchive = + new FileZipArchive(File(zipFile.getName()), zipFile) /** - * 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): ZipArchive = new URLZipArchive(url) +} - private[io] class ZipEntryTraversableClass(in: InputStream) extends ZipTrav { - val zis = () => new ZipInputStream(in) +trait ZipArchive extends AbstractFile { + self => - def foreach[U](f: ZipEntry => U) = { - var in: ZipInputStream = null - @tailrec def loop(): Unit = { - if (in.available == 0) - return + // The root of this archive, populated lazily. Implemented by File and URL. + def root: DirEntry - val entry = in.getNextEntry() - if (entry != null) { - f(entry) - in.closeEntry() - loop() - } - } + // AbstractFile members with common implementations in File and URL + override def lookupName(name: String, directory: Boolean) = root.lookupName(name, directory) + override def lookupNameUnchecked(name: String, directory: Boolean) = unsupported + override def iterator = root.iterator + override def isDirectory = true - try { - in = zis() - loop() - } - finally in.close() - } - } -} -import ZipArchive.ZipTrav + // Will be the root dir entry node after being populated. + protected val rootDirEntry = new DirEntry("/") + // Collected Paths -> DirEntries + private val dirs = newHashMap[DirEntry]() += ("/" -> rootDirEntry) + // Override if there's one available + def zipFile: ZipFile = null -/** 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 + sealed abstract class Entry(path: String) extends VirtualFile(baseName(path), path) { + def zipEntry: ZipEntry + def parent: DirEntry - /** 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 + override def container: DirEntry = parent + override def underlyingSource = Some(self) + final override def path = self + "(" + super.path + ")" + final override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] + final override def hashCode = super.path.## } + class DirEntry(path: String) extends Entry(path) { + var zipEntry: ZipEntry = _ + val entries = newHashMap[Entry]() - 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") + def parent = getDir(dirName(path)) override def lastModified: Long = - if (entry ne null) entry.getTime() else super.lastModified - + if (zipEntry ne null) zipEntry.getTime() else super.lastModified 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) + class FileEntry(path: String, archive: ZipFile, val zipEntry: ZipEntry) extends Entry(path) { + def parent = getDir(path) + override def input = archive getInputStream zipEntry + override def lastModified = zipEntry.getTime() + override def sizeOption = Some(zipEntry.getSize().toInt) } - class ZipRootCreator(f: ZipRootCreator => SourceType) { - val root = DirEntryConstructor(ZipContainer.this, "", "/") - - // Map from paths to DirEntries - val dirs = HashMap[String, DirEntryInterface]("/" -> root) - val traverser = ZipTravConstructor(creationSource) - private[this] var _parent: DirEntryInterface = _ - def parent = _parent + 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 start = path0.length - 1 + val path = if (path0.charAt(start) == '/') path0.substring(0, start) else path0 + var i = path.length - 1 - 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 { - _parent = getDir(dirs, pathFront(path)) - val name = pathRear(path) - _parent.entries(name) = FileEntryConstructor(f(this), name, path, entry) - } - } - - def apply() = { - traverser foreach addEntry - root - } - } - // Uglified for performance. - // protected def splitPath(path: String): (String, String) = { - // (path lastIndexOf '/') match { - // case -1 => ("/", path) - // case idx => path splitAt (idx + 1) - // } - // } - protected def splitPath(path: String, front: Boolean): String = { - var i = path.length - 1 while (i >= 0 && path.charAt(i) != '/') i -= 1 @@ -168,168 +111,84 @@ private[io] trait ZipContainer extends AbstractFile { if (front) path.substring(0, i + 1) else path.substring(i + 1) } - private def pathFront(path: String) = splitPath(path, true) - private def pathRear(path: String) = splitPath(path, false) - // End uglify. - - /** - * 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 + // Not using withDefault to be certain of performance. + private def newHashMap[T >: Null]() = + new mutable.HashMap[String, T] { override def default(key: String) = null } - /** - * 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. + /** If the given path entry has already been created, return the DirEntry. + * Otherwise create it and update both dirs and the parent's dirEntries. + * Not using getOrElseUpdate to be sure of performance. */ - protected def getDir(dirs: Map[String, DirEntryInterface], path: String): DirEntryInterface = - dirs.getOrElseUpdate(path, { - val pathTail = path.substring(0, path.length - 1) - val home = pathFront(pathTail) - val name = pathRear(pathTail) - 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 null, 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 = 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 - }) + private def getDir(path: String): DirEntry = { + if (dirs contains path) dirs(path) + else { + val parent = getDir(dirName(path)) + val newEntry = new DirEntry(path) + parent.entries(newEntry.name) = newEntry + dirs(path) = newEntry + newEntry + } } - final class DirEntry( - container: AbstractFile, - name: String, - path: String - ) extends Entry(container, name, path) with DirEntryInterface - { - def source = container - } + protected final def addZipEntry(entry: ZipEntry) { + val path = entry.getName + if (entry.isDirectory) { + val dir = getDir(path) + if (dir.zipEntry == null) + dir.zipEntry = entry + } + else { + val parent = getDir(dirName(path)) + val newEntry = new FileEntry(path, zipFile, entry) - 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 + parent.entries(newEntry.name) = newEntry + } } +} - 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 - } +final class FileZipArchive(file: File, override val zipFile: ZipFile) extends PlainFile(file) with ZipArchive { + lazy val root: DirEntry = { + val enum = zipFile.entries() + while (enum.hasMoreElements) + addZipEntry(enum.nextElement) + + rootDirEntry } } -/** - * 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 +final class URLZipArchive(url: URL) extends AbstractFile with ZipArchive { + lazy val root: DirEntry = { + val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input))) - protected lazy val creationSource = input - protected lazy val root = new ZipRootCreator(x => byteInputStream(x.traverser.zis()))() + @annotation.tailrec def loop() { + if (in.available != 0) { + val entry = in.getNextEntry() + if (entry != null) { + addZipEntry(entry) + in.closeEntry() + loop() + } + } + } + try loop() + finally in.close() - protected def DirEntryConstructor = (_, name, path) => new DirEntry(name, path) - protected def FileEntryConstructor = new FileEntry(_, _, _, _) - protected def ZipTravConstructor = new ZipArchive.ZipEntryTraversableClass(_) + rootDirEntry + } def name: String = url.getFile() def path: String = url.getPath() def input: InputStream = url.openStream() - def absolute: AbstractFile = this - def lastModified: Long = + 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 absolute = this + def file = null + def create() = unsupported + def delete() = unsupported + def output = unsupported def container = unsupported - - abstract class Entry(name: String, path: String) extends VirtualFile(name, path) { - final override def path = 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) - } } diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index c3731150c4..25c09b6c15 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -286,12 +286,12 @@ abstract class ClassPath[T] { case _ => None } - def sortString = asURLs map (_.toString) sorted + def sortString = join(split(asClasspathString).sorted: _*) override def equals(that: Any) = that match { case x: ClassPath[_] => this.sortString == x.sortString case _ => false } - override def hashCode = sortString.hashCode + override def hashCode = sortString.## } /** diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala index 1420499e91..1055bfdef2 100644 --- a/src/compiler/scala/tools/util/PathResolver.scala +++ b/src/compiler/scala/tools/util/PathResolver.scala @@ -105,10 +105,18 @@ object PathResolver { else if (scalaLibAsDir.isDirectory) scalaLibAsDir.path else "" - def scalaBootClassPath = scalaLibDirFound match { - case Some(dir) if scalaHomeExists => join(ClassPath expandDir dir.path: _*) - case _ => "" - } + // XXX It must be time for someone to figure out what all these things + // are intended to do. This is disabled here because it was causing all + // the scala jars to end up on the classpath twice: one on the boot + // classpath as set up by the runner (or regular classpath under -nobootcp) + // and then again here. + def scalaBootClassPath = "" + // scalaLibDirFound match { + // case Some(dir) if scalaHomeExists => + // val paths = ClassPath expandDir dir.path + // join(paths: _*) + // case _ => "" + // } def scalaExtDirs = Environment.scalaExtDirs -- cgit v1.2.3