From cef16f1ce9a1c0f6ed13e706f1ff52223dbbbe97 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 27 Aug 2020 15:22:29 +0200 Subject: [PATCH 1/4] Revert "Revert "Port classpath improvements"" This reverts commit a34b7b8948e1950ebdecf1ab8e18d91736b4941e. --- .../dotc/classpath/AggregateClassPath.scala | 71 +++++++++++------- .../tools/dotc/classpath/ClassPath.scala | 23 +++++- .../dotc/classpath/DirectoryClassPath.scala | 73 +++++++++---------- .../classpath/VirtualDirectoryClassPath.scala | 2 +- .../ZipAndJarFileLookupFactory.scala | 21 +++--- .../dotc/classpath/ZipArchiveFileLookup.scala | 48 +++++------- compiler/src/dotty/tools/io/ClassPath.scala | 44 ++++++++--- compiler/src/dotty/tools/io/ZipArchive.scala | 4 +- 8 files changed, 167 insertions(+), 119 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala index 6cf47f4b7a26..13d7e7a8b692 100644 --- a/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/AggregateClassPath.scala @@ -6,7 +6,9 @@ package dotc.classpath import java.net.URL import scala.collection.mutable.ArrayBuffer -import dotty.tools.io.{ AbstractFile, ClassPath, ClassRepresentation } +import scala.collection.immutable.ArraySeq + +import dotty.tools.io.{ AbstractFile, ClassPath, ClassRepresentation, EfficientClassPath } /** * A classpath unifying multiple class- and sourcepath entries. @@ -19,20 +21,20 @@ import dotty.tools.io.{ AbstractFile, ClassPath, ClassRepresentation } case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { override def findClassFile(className: String): Option[AbstractFile] = { val (pkg, _) = PackageNameUtils.separatePkgAndClassNames(className) - aggregatesForPackage(pkg).iterator.map(_.findClassFile(className)).collectFirst { + aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClassFile(className)).collectFirst { case Some(x) => x } } private val packageIndex: collection.mutable.Map[String, Seq[ClassPath]] = collection.mutable.Map() - private def aggregatesForPackage(pkg: String): Seq[ClassPath] = packageIndex.synchronized { - packageIndex.getOrElseUpdate(pkg, aggregates.filter(_.hasPackage(pkg))) + private def aggregatesForPackage(pkg: PackageName): Seq[ClassPath] = packageIndex.synchronized { + packageIndex.getOrElseUpdate(pkg.dottedString, aggregates.filter(_.hasPackage(pkg))) } override def findClass(className: String): Option[ClassRepresentation] = { val (pkg, _) = PackageNameUtils.separatePkgAndClassNames(className) def findEntry(isSource: Boolean): Option[ClassRepresentation] = - aggregatesForPackage(pkg).iterator.map(_.findClass(className)).collectFirst { + aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClass(className)).collectFirst { case Some(s: SourceFileEntry) if isSource => s case Some(s: ClassFileEntry) if !isSource => s } @@ -53,31 +55,47 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*) - override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { + override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = { val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct aggregatedPackages } - override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = + override private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = getDistinctEntries(_.classes(inPackage)) - override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = + override private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] = getDistinctEntries(_.sources(inPackage)) - override private[dotty] def hasPackage(pkg: String): Boolean = aggregates.exists(_.hasPackage(pkg)) - override private[dotty] def list(inPackage: String): ClassPathEntries = { - val (packages, classesAndSources) = aggregates.map { cp => - try - cp.list(inPackage).toTuple - catch { + override private[dotty] def hasPackage(pkg: PackageName): Boolean = aggregates.exists(_.hasPackage(pkg)) + override private[dotty] def list(inPackage: PackageName): ClassPathEntries = { + val packages: java.util.HashSet[PackageEntry] = new java.util.HashSet[PackageEntry]() + val classesAndSourcesBuffer = collection.mutable.ArrayBuffer[ClassRepresentation]() + val onPackage: PackageEntry => Unit = packages.add(_) + val onClassesAndSources: ClassRepresentation => Unit = classesAndSourcesBuffer += _ + + aggregates.foreach { cp => + try { + cp match { + case ecp: EfficientClassPath => + ecp.list(inPackage, onPackage, onClassesAndSources) + case _ => + val entries = cp.list(inPackage) + entries._1.foreach(entry => packages.add(entry)) + classesAndSourcesBuffer ++= entries._2 + } + } catch { case ex: java.io.IOException => - val e = new FatalError(ex.getMessage) + val e = FatalError(ex.getMessage) e.initCause(ex) throw e } - }.unzip - val distinctPackages = packages.flatten.distinct - val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*) + } + + val distinctPackages: Seq[PackageEntry] = { + val arr = packages.toArray(new Array[PackageEntry](packages.size())) + ArraySeq.unsafeWrapArray(arr) + } + val distinctClassesAndSources = mergeClassesAndSources(classesAndSourcesBuffer) ClassPathEntries(distinctPackages, distinctClassesAndSources) } @@ -86,19 +104,16 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { * creates an entry containing both of them. If there would be more than one class or source * entries for the same class it always would use the first entry of each type found on a classpath. */ - private def mergeClassesAndSources(entries: scala.collection.Seq[ClassRepresentation]*): Seq[ClassRepresentation] = { + private def mergeClassesAndSources(entries: scala.collection.Seq[ClassRepresentation]): Seq[ClassRepresentation] = { // based on the implementation from MergedClassPath var count = 0 - val indices = collection.mutable.HashMap[String, Int]() - val mergedEntries = new ArrayBuffer[ClassRepresentation](1024) - + val indices = new collection.mutable.HashMap[String, Int]() + val mergedEntries = new ArrayBuffer[ClassRepresentation](entries.size) for { - partOfEntries <- entries - entry <- partOfEntries - } - { + entry <- entries + } { val name = entry.name - if (indices contains name) { + if (indices.contains(name)) { val index = indices(name) val existing = mergedEntries(index) @@ -113,7 +128,7 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { count += 1 } } - mergedEntries.toIndexedSeq + if (mergedEntries.isEmpty) Nil else mergedEntries.toIndexedSeq } private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Seq[EntryType]): Seq[EntryType] = { diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala index 9557a55e25d2..714a02cfc93a 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala @@ -10,6 +10,10 @@ case class ClassPathEntries(packages: scala.collection.Seq[PackageEntry], classe def toTuple: (scala.collection.Seq[PackageEntry], scala.collection.Seq[ClassRepresentation]) = (packages, classesAndSources) } +object ClassPathEntries { + val empty = ClassPathEntries(Seq.empty, Seq.empty) +} + trait ClassFileEntry extends ClassRepresentation { def file: AbstractFile } @@ -18,6 +22,21 @@ trait SourceFileEntry extends ClassRepresentation { def file: AbstractFile } +case class PackageName(dottedString: String) { + def isRoot: Boolean = dottedString.isEmpty + val dirPathTrailingSlash: String = FileUtils.dirPath(dottedString) + "/" + + def entryName(entry: String): String = { + if (isRoot) entry else { + val builder = new java.lang.StringBuilder(dottedString.length + 1 + entry.length) + builder.append(dottedString) + builder.append('.') + builder.append(entry) + builder.toString + } + } +} + trait PackageEntry { def name: String } @@ -50,10 +69,10 @@ private[dotty] case class PackageEntryImpl(name: String) extends PackageEntry private[dotty] trait NoSourcePaths { def asSourcePathString: String = "" - private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty + private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] = Seq.empty } private[dotty] trait NoClassPaths { def findClassFile(className: String): Option[AbstractFile] = None - private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty + private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = Seq.empty } diff --git a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala index dc5504cffba0..46f2fd1c7d62 100644 --- a/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala @@ -7,9 +7,11 @@ import java.io.{File => JFile} import java.net.URL import java.nio.file.{FileSystems, Files} -import dotty.tools.io.{AbstractFile, PlainFile, ClassPath, ClassRepresentation} +import dotty.tools.io.{AbstractFile, PlainFile, ClassPath, ClassRepresentation, EfficientClassPath} import FileUtils._ + import scala.collection.JavaConverters._ +import scala.collection.immutable.ArraySeq /** * A trait allowing to look for classpath entries in directories. It provides common logic for @@ -18,7 +20,7 @@ import scala.collection.JavaConverters._ * when we have a name of a package. * It abstracts over the file representation to work with both JFile and AbstractFile. */ -trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath { +trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends EfficientClassPath { type F val dir: F @@ -33,27 +35,24 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath { protected def createFileEntry(file: AbstractFile): FileEntryType protected def isMatchingFile(f: F): Boolean - private def getDirectory(forPackage: String): Option[F] = - if (forPackage == ClassPath.RootPackage) + private def getDirectory(forPackage: PackageName): Option[F] = + if (forPackage.isRoot) Some(dir) - else { - val packageDirName = FileUtils.dirPath(forPackage) - getSubDir(packageDirName) - } + else + getSubDir(forPackage.dirPathTrailingSlash) - override private[dotty] def hasPackage(pkg: String): Boolean = getDirectory(pkg).isDefined + override private[dotty] def hasPackage(pkg: PackageName): Boolean = getDirectory(pkg).isDefined - private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { + private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = { val dirForPackage = getDirectory(inPackage) val nestedDirs: Array[F] = dirForPackage match { case None => emptyFiles case Some(directory) => listChildren(directory, Some(isPackage)) } - val prefix = PackageNameUtils.packagePrefix(inPackage) - nestedDirs.toIndexedSeq.map(f => PackageEntryImpl(prefix + getName(f))) + ArraySeq.unsafeWrapArray(nestedDirs).map(f => PackageEntryImpl(inPackage.entryName(getName(f)))) } - protected def files(inPackage: String): Seq[FileEntryType] = { + protected def files(inPackage: PackageName): Seq[FileEntryType] = { val dirForPackage = getDirectory(inPackage) val files: Array[F] = dirForPackage match { case None => emptyFiles @@ -62,21 +61,18 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath { files.iterator.map(f => createFileEntry(toAbstractFile(f))).toSeq } - private[dotty] def list(inPackage: String): ClassPathEntries = { + override def list(inPackage: PackageName, onPackageEntry: PackageEntry => Unit, onClassesAndSources: ClassRepresentation => Unit): Unit = { val dirForPackage = getDirectory(inPackage) - val files: Array[F] = dirForPackage match { - case None => emptyFiles - case Some(directory) => listChildren(directory) + dirForPackage match { + case None => + case Some(directory) => + for (file <- listChildren(directory)) { + if (isPackage(file)) + onPackageEntry(PackageEntryImpl(inPackage.entryName(getName(file)))) + else if (isMatchingFile(file)) + onClassesAndSources(createFileEntry(toAbstractFile(file))) + } } - val packagePrefix = PackageNameUtils.packagePrefix(inPackage) - val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] - val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType] - for (file <- files) - if (isPackage(file)) - packageBuf += PackageEntryImpl(packagePrefix + getName(file)) - else if (isMatchingFile(file)) - fileBuf += createFileEntry(toAbstractFile(file)) - ClassPathEntries(packageBuf, fileBuf) } } @@ -159,24 +155,25 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No } /** Empty string represents root package */ - override private[dotty] def hasPackage(pkg: String): Boolean = packageToModuleBases.contains(pkg) + override private[dotty] def hasPackage(pkg: PackageName): Boolean = packageToModuleBases.contains(pkg.dottedString) - override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { + override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = { def matches(packageDottedName: String) = if (packageDottedName.contains(".")) - packageOf(packageDottedName) == inPackage - else inPackage == "" + packageOf(packageDottedName) == inPackage.dottedString + else inPackage.isRoot packageToModuleBases.keysIterator.filter(matches).map(PackageEntryImpl(_)).toVector } - private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = - if (inPackage == "") Nil + + private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = + if (inPackage.isRoot) Nil else - packageToModuleBases.getOrElse(inPackage, Nil).flatMap(x => - Files.list(x.resolve(FileUtils.dirPath(inPackage))).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x => + packageToModuleBases.getOrElse(inPackage.dottedString, Nil).flatMap(x => + Files.list(x.resolve(inPackage.dirPathTrailingSlash)).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x => ClassFileEntryImpl(new PlainFile(new dotty.tools.io.File(x)))).toVector - override private[dotty] def list(inPackage: String): ClassPathEntries = - if (inPackage == "") ClassPathEntries(packages(inPackage), Nil) + override private[dotty] def list(inPackage: PackageName): ClassPathEntries = + if (inPackage.isRoot) ClassPathEntries(packages(inPackage), Nil) else ClassPathEntries(packages(inPackage), classes(inPackage)) def asURLs: Seq[URL] = Seq(new URL("jrt:/")) @@ -214,7 +211,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) protected def isMatchingFile(f: JFile): Boolean = f.isClass - private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) + private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage) } case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFileEntryImpl] with NoClassPaths { @@ -238,5 +235,5 @@ case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFi } } - private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) + private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] = files(inPackage) } diff --git a/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala index 92f462ec0c30..bea07504212b 100644 --- a/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala @@ -45,7 +45,7 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi Option(lookupPath(dir)(relativePath.split(java.io.File.separator).toIndexedSeq, directory = false)) } - private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) + private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage) protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) protected def isMatchingFile(f: AbstractFile): Boolean = f.isClass diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala index 3b4c79feda0d..9ddec8ebbdf7 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala @@ -42,16 +42,16 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory { override def findClassFile(className: String): Option[AbstractFile] = { val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) - file(pkg, simpleClassName + ".class").map(_.file) + file(PackageName(pkg), simpleClassName + ".class").map(_.file) } // This method is performance sensitive as it is used by SBT's ExtractDependencies phase. override def findClass(className: String): Option[ClassRepresentation] = { val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) - file(pkg, simpleClassName + ".class") + file(PackageName(pkg), simpleClassName + ".class") } - override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) + override private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage) override protected def createFileEntry(file: FileZipArchive#Entry): ClassFileEntryImpl = ClassFileEntryImpl(file) override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isClass @@ -67,7 +67,7 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory { private case class ManifestResourcesClassPath(file: ManifestResources) extends ClassPath with NoSourcePaths { override def findClassFile(className: String): Option[AbstractFile] = { val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) - classes(pkg).find(_.name == simpleClassName).map(_.file) + classes(PackageName(pkg)).find(_.name == simpleClassName).map(_.file) } override def asClassPathStrings: Seq[String] = Seq(file.path) @@ -118,21 +118,20 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory { packages } - override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = cachedPackages.get(inPackage) match { + override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = cachedPackages.get(inPackage.dottedString) match { case None => Seq.empty case Some(PackageFileInfo(_, subpackages)) => - val prefix = PackageNameUtils.packagePrefix(inPackage) - subpackages.map(packageFile => PackageEntryImpl(prefix + packageFile.name)) + subpackages.map(packageFile => PackageEntryImpl(inPackage.entryName(packageFile.name))) } - override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = cachedPackages.get(inPackage) match { + override private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = cachedPackages.get(inPackage.dottedString) match { case None => Seq.empty case Some(PackageFileInfo(pkg, _)) => (for (file <- pkg if file.isClass) yield ClassFileEntryImpl(file)).toSeq } - override private[dotty] def hasPackage(pkg: String) = cachedPackages.contains(pkg) - override private[dotty] def list(inPackage: String): ClassPathEntries = ClassPathEntries(packages(inPackage), classes(inPackage)) + override private[dotty] def hasPackage(pkg: PackageName) = cachedPackages.contains(pkg.dottedString) + override private[dotty] def list(inPackage: PackageName): ClassPathEntries = ClassPathEntries(packages(inPackage), classes(inPackage)) } private object ManifestResourcesClassPath { @@ -164,7 +163,7 @@ object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory { override def asSourcePathString: String = asClassPathString - override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage) + override private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] = files(inPackage) override protected def createFileEntry(file: FileZipArchive#Entry): SourceFileEntryImpl = SourceFileEntryImpl(file) override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isScalaOrJavaSource diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala index 35a4c89c3f72..790406b012d2 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala @@ -8,14 +8,14 @@ import java.net.URL import dotty.tools.io.{ AbstractFile, FileZipArchive } import FileUtils._ -import dotty.tools.io.{ClassPath, ClassRepresentation} +import dotty.tools.io.{EfficientClassPath, ClassRepresentation} /** * A trait allowing to look for classpath entries of given type in zip and jar files. * It provides common logic for classes handling class and source files. * It's aware of things like e.g. META-INF directory which is correctly skipped. */ -trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends ClassPath { +trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends EfficientClassPath { val zipFile: File assert(zipFile != null, "Zip file in ZipArchiveFileLookup cannot be null") @@ -25,23 +25,22 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends ClassPa private val archive = new FileZipArchive(zipFile.toPath) - override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { - val prefix = PackageNameUtils.packagePrefix(inPackage) + override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = { for { dirEntry <- findDirEntry(inPackage).toSeq entry <- dirEntry.iterator if entry.isPackage } - yield PackageEntryImpl(prefix + entry.name) + yield PackageEntryImpl(inPackage.entryName(entry.name)) } - protected def files(inPackage: String): Seq[FileEntryType] = + protected def files(inPackage: PackageName): Seq[FileEntryType] = for { dirEntry <- findDirEntry(inPackage).toSeq entry <- dirEntry.iterator if isRequiredFileType(entry) } yield createFileEntry(entry) - protected def file(inPackage: String, name: String): Option[FileEntryType] = + protected def file(inPackage: PackageName, name: String): Option[FileEntryType] = for { dirEntry <- findDirEntry(inPackage) entry <- Option(dirEntry.lookupName(name, directory = false)) @@ -49,28 +48,21 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends ClassPa } yield createFileEntry(entry) - override private[dotty] def hasPackage(pkg: String): Boolean = findDirEntry(pkg).isDefined - override private[dotty] def list(inPackage: String): ClassPathEntries = { - val foundDirEntry = findDirEntry(inPackage) - - foundDirEntry map { dirEntry => - val pkgBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] - val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType] - val prefix = PackageNameUtils.packagePrefix(inPackage) - - for (entry <- dirEntry.iterator) - if (entry.isPackage) - pkgBuf += PackageEntryImpl(prefix + entry.name) - else if (isRequiredFileType(entry)) - fileBuf += createFileEntry(entry) - ClassPathEntries(pkgBuf, fileBuf) - } getOrElse ClassPathEntries(Seq.empty, Seq.empty) - } + override def hasPackage(pkg: PackageName) = findDirEntry(pkg).isDefined + def list(inPackage: PackageName, onPackageEntry: PackageEntry => Unit, onClassesAndSources: ClassRepresentation => Unit): Unit = + findDirEntry(inPackage) match { + case Some(dirEntry) => + for (entry <- dirEntry.iterator) { + if (entry.isPackage) + onPackageEntry(PackageEntryImpl(inPackage.entryName(entry.name))) + else if (isRequiredFileType(entry)) + onClassesAndSources(createFileEntry(entry)) + } + case None => + } - private def findDirEntry(pkg: String): Option[archive.DirEntry] = { - val dirName = pkg.replace('.', '/') + "/" - archive.allDirs.get(dirName) - } + private def findDirEntry(pkg: PackageName): Option[archive.DirEntry] = + archive.allDirs.get(pkg.dirPathTrailingSlash) protected def createFileEntry(file: FileZipArchive#Entry): FileEntryType protected def isRequiredFileType(file: AbstractFile): Boolean diff --git a/compiler/src/dotty/tools/io/ClassPath.scala b/compiler/src/dotty/tools/io/ClassPath.scala index 97d678901a6d..d7218108e944 100644 --- a/compiler/src/dotty/tools/io/ClassPath.scala +++ b/compiler/src/dotty/tools/io/ClassPath.scala @@ -14,6 +14,8 @@ import java.util.regex.PatternSyntaxException import File.pathSeparator import Jar.isJarOrZip +import dotc.classpath.{ PackageEntry, ClassPathEntries, PackageName } + /** * A representation of the compiler's class- or sourcepath. */ @@ -21,6 +23,12 @@ trait ClassPath { import dotty.tools.dotc.classpath._ def asURLs: Seq[URL] + final def hasPackage(pkg: String): Boolean = hasPackage(PackageName(pkg)) + final def packages(inPackage: String): Seq[PackageEntry] = packages(PackageName(inPackage)) + final def classes(inPackage: String): Seq[ClassFileEntry] = classes(PackageName(inPackage)) + final def sources(inPackage: String): Seq[SourceFileEntry] = sources(PackageName(inPackage)) + final def list(inPackage: String): ClassPathEntries = list(PackageName(inPackage)) + /* * These methods are mostly used in the ClassPath implementation to implement the `list` and * `findX` methods below. @@ -29,22 +37,22 @@ trait ClassPath { * which is used by the repl's `:require` (and maybe the spark repl, https://github.com/scala/scala/pull/4051). * Using these methods directly is more efficient than calling `list`. * - * The `inPackage` string is a full package name, e.g. "" or "scala.collection". + * The `inPackage` contains a full package name, e.g. "" or "scala.collection". */ - private[dotty] def hasPackage(pkg: String): Boolean - private[dotty] def packages(inPackage: String): Seq[PackageEntry] - private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] - private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] + private[dotty] def hasPackage(pkg: PackageName): Boolean + private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] + private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] + private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] /** * Returns packages and classes (source or classfile) that are members of `inPackage` (not - * recursively). The `inPackage` string is a full package name, e.g., "scala.collection". + * recursively). The `inPackage` contains a full package name, e.g., "scala.collection". * * This is the main method uses to find classes, see class `PackageLoader`. The * `rootMirror.rootLoader` is created with `inPackage = ""`. */ - private[dotty] def list(inPackage: String): ClassPathEntries + private[dotty] def list(inPackage: PackageName): ClassPathEntries /** * Returns the class file and / or source file for a given external name, e.g., "java.lang.String". @@ -63,8 +71,9 @@ trait ClassPath { // solution for a given type of ClassPath val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className) - val foundClassFromClassFiles = classes(pkg).find(_.name == simpleClassName) - def findClassInSources = sources(pkg).find(_.name == simpleClassName) + val packageName = PackageName(pkg) + val foundClassFromClassFiles = classes(packageName).find(_.name == simpleClassName) + def findClassInSources = sources(packageName).find(_.name == simpleClassName) foundClassFromClassFiles orElse findClassInSources } @@ -94,6 +103,23 @@ trait ClassPath { def asSourcePathString: String } +trait EfficientClassPath extends ClassPath { + def list(inPackage: PackageName, onPackageEntry: PackageEntry => Unit, onClassesAndSources: ClassRepresentation => Unit): Unit + + override def list(inPackage: PackageName): ClassPathEntries = { + val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry] + val classRepBuf = collection.mutable.ArrayBuffer.empty[ClassRepresentation] + list(inPackage, packageBuf += _, classRepBuf += _) + if (packageBuf.isEmpty && classRepBuf.isEmpty) ClassPathEntries.empty + else ClassPathEntries(packageBuf, classRepBuf) + } +} + +trait EfficientClassPathCallBack { + def packageEntry(entry: PackageEntry): Unit + def classesAndSources(entry: ClassRepresentation): Unit +} + object ClassPath { val RootPackage: String = "" diff --git a/compiler/src/dotty/tools/io/ZipArchive.scala b/compiler/src/dotty/tools/io/ZipArchive.scala index 15b714718eb3..8384b9bd1a38 100644 --- a/compiler/src/dotty/tools/io/ZipArchive.scala +++ b/compiler/src/dotty/tools/io/ZipArchive.scala @@ -96,7 +96,7 @@ abstract class ZipArchive(override val jpath: JPath) extends AbstractFile with E // }) dirs get path match { case Some(v) => v - case None => + case None => val parent = ensureDir(dirs, dirName(path)) val dir = new DirEntry(path, parent) parent.entries(baseName(path)) = dir @@ -199,7 +199,7 @@ final class FileZipArchive(jpath: JPath) extends ZipArchive(jpath) { final class ManifestResources(val url: URL) extends ZipArchive(null) { def iterator(): Iterator[AbstractFile] = { val root = new DirEntry("/", null) - val dirs = mutable.HashMap[String, DirEntry]("/" -> root) + val dirs = new mutable.HashMap[String, DirEntry]; dirs.put("/", root) val manifest = new Manifest(input) val iter = manifest.getEntries().keySet().iterator().asScala.filter(_.endsWith(".class")).map(new ZipEntry(_)) From 19e6f77a75c7732d6ac3706182e5c09bab485daa Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 27 Aug 2020 15:25:35 +0200 Subject: [PATCH 2/4] Code refactor --- compiler/src/dotty/tools/io/ZipArchive.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/io/ZipArchive.scala b/compiler/src/dotty/tools/io/ZipArchive.scala index 8384b9bd1a38..4329c1311f31 100644 --- a/compiler/src/dotty/tools/io/ZipArchive.scala +++ b/compiler/src/dotty/tools/io/ZipArchive.scala @@ -199,7 +199,7 @@ final class FileZipArchive(jpath: JPath) extends ZipArchive(jpath) { final class ManifestResources(val url: URL) extends ZipArchive(null) { def iterator(): Iterator[AbstractFile] = { val root = new DirEntry("/", null) - val dirs = new mutable.HashMap[String, DirEntry]; dirs.put("/", root) + val dirs = mutable.HashMap[String, DirEntry]("/" -> root) val manifest = new Manifest(input) val iter = manifest.getEntries().keySet().iterator().asScala.filter(_.endsWith(".class")).map(new ZipEntry(_)) From 3ece9d5751384d138c635765076d28656628f3c9 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 27 Aug 2020 17:52:07 +0200 Subject: [PATCH 3/4] Fix windows bootstrap --- compiler/src/dotty/tools/dotc/classpath/ClassPath.scala | 2 +- compiler/src/dotty/tools/dotc/classpath/FileUtils.scala | 2 ++ compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala | 3 +-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala index 714a02cfc93a..e7811c267dff 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala @@ -24,7 +24,7 @@ trait SourceFileEntry extends ClassRepresentation { case class PackageName(dottedString: String) { def isRoot: Boolean = dottedString.isEmpty - val dirPathTrailingSlash: String = FileUtils.dirPath(dottedString) + "/" + val dirPathTrailingSlash: String = FileUtils.dirPathInArchive(dottedString) + "/" def entryName(entry: String): String = { if (isRoot) entry else { diff --git a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala index d39ca0c11d12..32ee4a7704cc 100644 --- a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala +++ b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala @@ -44,6 +44,8 @@ object FileUtils { def dirPath(forPackage: String): String = forPackage.replace('.', JFile.separatorChar) + def dirPathInArchive(forPackage: String): String = forPackage.replace('.', '/') + def endsClass(fileName: String): Boolean = fileName.length > 6 && fileName.substring(fileName.length - 6) == ".class" diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 52431854f64b..4f98f791c3cd 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -209,8 +209,7 @@ object SymbolLoaders { /** Load contents of a package */ - class PackageLoader(_sourceModule: TermSymbol, classPath: ClassPath) - extends SymbolLoader { + class PackageLoader(_sourceModule: TermSymbol, classPath: ClassPath) extends SymbolLoader { override def sourceFileOrNull: AbstractFile = null override def sourceModule(using Context): TermSymbol = _sourceModule def description(using Context): String = "package loader " + sourceModule.fullName From 4c5c00cc55c465eed9721bd96213f7fd058d77aa Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 27 Aug 2020 20:40:18 +0200 Subject: [PATCH 4/4] Handle usage of dirPathTrailingSlash in DirectoryPath `dirPathTrailingSlash` is used both in `DirectoryPath` and `ZipArchiveFileLookup`, the former is platform-depndent, while the latter always uses `/` as separator. Previously, the code works by accident: `new JFile(dir, path)` and `dir.resolve(path)` are able to handle `path = "java/lang"` on windows. --- compiler/src/dotty/tools/dotc/classpath/ClassPath.scala | 9 ++++++++- compiler/src/dotty/tools/dotc/classpath/FileUtils.scala | 2 +- .../tools/dotc/classpath/ZipArchiveFileLookup.scala | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala index e7811c267dff..176b6acf9c6c 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ClassPath.scala @@ -23,8 +23,15 @@ trait SourceFileEntry extends ClassRepresentation { } case class PackageName(dottedString: String) { + val dirPathTrailingSlashJar: String = FileUtils.dirPathInJar(dottedString) + "/" + + val dirPathTrailingSlash: String = + if (java.io.File.separatorChar == '/') + dirPathTrailingSlashJar + else + FileUtils.dirPath(dottedString) + java.io.File.separator + def isRoot: Boolean = dottedString.isEmpty - val dirPathTrailingSlash: String = FileUtils.dirPathInArchive(dottedString) + "/" def entryName(entry: String): String = { if (isRoot) entry else { diff --git a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala index 32ee4a7704cc..83813a9c4fb5 100644 --- a/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala +++ b/compiler/src/dotty/tools/dotc/classpath/FileUtils.scala @@ -44,7 +44,7 @@ object FileUtils { def dirPath(forPackage: String): String = forPackage.replace('.', JFile.separatorChar) - def dirPathInArchive(forPackage: String): String = forPackage.replace('.', '/') + def dirPathInJar(forPackage: String): String = forPackage.replace('.', '/') def endsClass(fileName: String): Boolean = fileName.length > 6 && fileName.substring(fileName.length - 6) == ".class" diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala index 790406b012d2..a06fc429876a 100644 --- a/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala +++ b/compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala @@ -62,7 +62,7 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends Efficie } private def findDirEntry(pkg: PackageName): Option[archive.DirEntry] = - archive.allDirs.get(pkg.dirPathTrailingSlash) + archive.allDirs.get(pkg.dirPathTrailingSlashJar) protected def createFileEntry(file: FileZipArchive#Entry): FileEntryType protected def isRequiredFileType(file: AbstractFile): Boolean