Skip to content

Commit 996bef3

Browse files
committed
Another take on avoiding garbage in classpath lookups
1 parent 86d3564 commit 996bef3

File tree

8 files changed

+103
-90
lines changed

8 files changed

+103
-90
lines changed

src/compiler/scala/tools/nsc/classpath/AggregateClassPath.scala

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,21 @@ import scala.tools.nsc.util.ClassRepresentation
3030
case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
3131
override def findClassFile(className: String): Option[AbstractFile] = {
3232
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
33-
aggregatesForPackage(pkg).iterator.map(_.findClassFile(className)).collectFirst {
33+
aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClassFile(className)).collectFirst {
3434
case Some(x) => x
3535
}
3636
}
3737
private[this] val packageIndex: collection.mutable.Map[String, Seq[ClassPath]] = collection.mutable.Map()
38-
private def aggregatesForPackage(pkg: String): Seq[ClassPath] = packageIndex.synchronized {
39-
packageIndex.getOrElseUpdate(pkg, aggregates.filter(_.hasPackage(pkg)))
38+
private def aggregatesForPackage(pkg: PackageName): Seq[ClassPath] = packageIndex.synchronized {
39+
packageIndex.getOrElseUpdate(pkg.dottedString, aggregates.filter(_.hasPackage(pkg)))
4040
}
4141

4242
// This method is performance sensitive as it is used by SBT's ExtractDependencies phase.
4343
override def findClass(className: String): Option[ClassRepresentation] = {
4444
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
4545

4646
def findEntry(isSource: Boolean): Option[ClassRepresentation] = {
47-
aggregatesForPackage(pkg).iterator.map(_.findClass(className)).collectFirst {
47+
aggregatesForPackage(PackageName(pkg)).iterator.map(_.findClass(className)).collectFirst {
4848
case Some(s: SourceFileEntry) if isSource => s
4949
case Some(s: ClassFileEntry) if !isSource => s
5050
}
@@ -66,19 +66,20 @@ case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath {
6666

6767
override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*)
6868

69-
override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
69+
override private[nsc] def packages(inPackage: PackageName): Seq[PackageEntry] = {
7070
val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct
7171
aggregatedPackages
7272
}
7373

74-
override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] =
74+
override private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] =
7575
getDistinctEntries(_.classes(inPackage))
7676

77-
override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] =
77+
override private[nsc] def sources(inPackage: PackageName): Seq[SourceFileEntry] =
7878
getDistinctEntries(_.sources(inPackage))
7979

80-
override private[nsc] def hasPackage(pkg: String) = aggregates.exists(_.hasPackage(pkg))
81-
override private[nsc] def list(inPackage: String): ClassPathEntries = {
80+
override private[nsc] def hasPackage(pkg: PackageName) = aggregates.exists(_.hasPackage(pkg))
81+
override private[nsc] def list(inPackage: PackageName): ClassPathEntries = {
82+
8283
val (packages, classesAndSources) = aggregates.map { cp =>
8384
try {
8485
cp.list(inPackage)

src/compiler/scala/tools/nsc/classpath/ClassPath.scala

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ trait SourceFileEntry extends ClassRepresentation {
3232
def file: AbstractFile
3333
}
3434

35+
case class PackageName(dottedString: String) {
36+
def isRoot: Boolean = dottedString.isEmpty
37+
val dirPathTrailingSlash: String = FileUtils.dirPath(dottedString) + "/"
38+
39+
def entryName(entry: String): String = {
40+
if (isRoot) entry else {
41+
val builder = new java.lang.StringBuilder(dottedString.length + 1 + entry.length)
42+
builder.append(dottedString)
43+
builder.append('.')
44+
builder.append(entry)
45+
builder.toString
46+
}
47+
}
48+
}
49+
3550
trait PackageEntry {
3651
def name: String
3752
}
@@ -61,10 +76,10 @@ private[nsc] case class PackageEntryImpl(name: String) extends PackageEntry
6176

6277
private[nsc] trait NoSourcePaths {
6378
final def asSourcePathString: String = ""
64-
final private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = Seq.empty
79+
final private[nsc] def sources(inPackage: PackageName): Seq[SourceFileEntry] = Seq.empty
6580
}
6681

6782
private[nsc] trait NoClassPaths {
6883
final def findClassFile(className: String): Option[AbstractFile] = None
69-
private[nsc] final def classes(inPackage: String): Seq[ClassFileEntry] = Seq.empty
84+
private[nsc] final def classes(inPackage: PackageName): Seq[ClassFileEntry] = Seq.empty
7085
}

src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,28 +47,26 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
4747
protected def createFileEntry(file: AbstractFile): FileEntryType
4848
protected def isMatchingFile(f: F): Boolean
4949

50-
private def getDirectory(forPackage: String): Option[F] = {
51-
if (forPackage == ClassPath.RootPackage) {
50+
private def getDirectory(forPackage: PackageName): Option[F] = {
51+
if (forPackage.isRoot) {
5252
Some(dir)
5353
} else {
54-
val packageDirName = FileUtils.dirPath(forPackage)
55-
getSubDir(packageDirName)
54+
getSubDir(forPackage.dirPathTrailingSlash)
5655
}
5756
}
58-
override private[nsc] def hasPackage(pkg: String) = getDirectory(pkg).isDefined
57+
override private[nsc] def hasPackage(pkg: PackageName) = getDirectory(pkg).isDefined
5958

60-
private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
59+
private[nsc] def packages(inPackage: PackageName): Seq[PackageEntry] = {
6160
val dirForPackage = getDirectory(inPackage)
6261

6362
val nestedDirs: Array[F] = dirForPackage match {
6463
case None => emptyFiles
6564
case Some(directory) => listChildren(directory, Some(isPackage))
6665
}
67-
val prefix = PackageNameUtils.packagePrefix(inPackage)
68-
nestedDirs.map(f => PackageEntryImpl(prefix + getName(f)))
66+
nestedDirs.map(f => PackageEntryImpl(inPackage.entryName(getName(f))))
6967
}
7068

71-
protected def files(inPackage: String): Seq[FileEntryType] = {
69+
protected def files(inPackage: PackageName): Seq[FileEntryType] = {
7270
val dirForPackage = getDirectory(inPackage)
7371
val files: Array[F] = dirForPackage match {
7472
case None => emptyFiles
@@ -77,18 +75,17 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
7775
files.map(f => createFileEntry(toAbstractFile(f)))
7876
}
7977

80-
private[nsc] def list(inPackage: String): ClassPathEntries = {
78+
private[nsc] def list(inPackage: PackageName): ClassPathEntries = {
8179
val dirForPackage = getDirectory(inPackage)
8280
val files: Array[F] = dirForPackage match {
8381
case None => emptyFiles
8482
case Some(directory) => listChildren(directory)
8583
}
86-
val packagePrefix = PackageNameUtils.packagePrefix(inPackage)
8784
val packageBuf = collection.mutable.ArrayBuffer.empty[PackageEntry]
8885
val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType]
8986
for (file <- files) {
9087
if (isPackage(file))
91-
packageBuf += PackageEntryImpl(packagePrefix + getName(file))
88+
packageBuf += PackageEntryImpl(inPackage.entryName(getName(file)))
9289
else if (isMatchingFile(file))
9390
fileBuf += createFileEntry(toAbstractFile(file))
9491
}
@@ -196,21 +193,21 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
196193
}
197194

198195
/** Empty string represents root package */
199-
override private[nsc] def hasPackage(pkg: String) = packageToModuleBases.contains(pkg)
200-
override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
201-
packageToModuleBases.keysIterator.filter(pack => packageContains(inPackage, pack)).map(PackageEntryImpl(_)).toVector
196+
override private[nsc] def hasPackage(pkg: PackageName) = packageToModuleBases.contains(pkg.dottedString)
197+
override private[nsc] def packages(inPackage: PackageName): Seq[PackageEntry] = {
198+
packageToModuleBases.keysIterator.filter(pack => packageContains(inPackage.dottedString, pack)).map(PackageEntryImpl(_)).toVector
202199
}
203-
private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = {
204-
if (inPackage == "") Nil
200+
private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] = {
201+
if (inPackage.isRoot) Nil
205202
else {
206-
packageToModuleBases.getOrElse(inPackage, Nil).flatMap(x =>
207-
Files.list(x.resolve(inPackage.replace('.', '/'))).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
203+
packageToModuleBases.getOrElse(inPackage.dottedString, Nil).flatMap(x =>
204+
Files.list(x.resolve(inPackage.dirPathTrailingSlash)).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
208205
ClassFileEntryImpl(new PlainNioFile(x))).toVector
209206
}
210207
}
211208

212-
override private[nsc] def list(inPackage: String): ClassPathEntries =
213-
if (inPackage == "") ClassPathEntries(packages(inPackage), Nil)
209+
override private[nsc] def list(inPackage: PackageName): ClassPathEntries =
210+
if (inPackage.isRoot) ClassPathEntries(packages(inPackage), Nil)
214211
else ClassPathEntries(packages(inPackage), classes(inPackage))
215212

216213
def asURLs: Seq[URL] = Seq(new URL("jrt:/"))
@@ -262,21 +259,21 @@ final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends Clas
262259
}
263260

264261
/** Empty string represents root package */
265-
override private[nsc] def hasPackage(pkg: String) = packageIndex.contains(pkg)
266-
override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
267-
packageIndex.keysIterator.filter(pack => packageContains(inPackage, pack)).map(PackageEntryImpl(_)).toVector
262+
override private[nsc] def hasPackage(pkg: PackageName) = packageIndex.contains(pkg.dottedString)
263+
override private[nsc] def packages(inPackage: PackageName): Seq[PackageEntry] = {
264+
packageIndex.keysIterator.filter(pack => packageContains(inPackage.dottedString, pack)).map(PackageEntryImpl(_)).toVector
268265
}
269-
private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = {
270-
if (inPackage == "") Nil
266+
private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] = {
267+
if (inPackage.isRoot) Nil
271268
else {
272-
val sigFiles = packageIndex.getOrElse(inPackage, Nil).iterator.flatMap(p =>
269+
val sigFiles = packageIndex.getOrElse(inPackage.dottedString, Nil).iterator.flatMap(p =>
273270
Files.list(p).iterator().asScala.filter(_.getFileName.toString.endsWith(".sig")))
274271
sigFiles.map(f => ClassFileEntryImpl(new PlainNioFile(f))).toVector
275272
}
276273
}
277274

278-
override private[nsc] def list(inPackage: String): ClassPathEntries =
279-
if (inPackage == "") ClassPathEntries(packages(inPackage), Nil)
275+
override private[nsc] def list(inPackage: PackageName): ClassPathEntries =
276+
if (inPackage.isRoot) ClassPathEntries(packages(inPackage), Nil)
280277
else ClassPathEntries(packages(inPackage), classes(inPackage))
281278

282279
def asURLs: Seq[URL] = Nil
@@ -310,7 +307,7 @@ case class DirectoryClassPath(dir: File) extends JFileDirectoryLookup[ClassFileE
310307
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
311308
protected def isMatchingFile(f: File): Boolean = f.isClass
312309

313-
private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
310+
private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
314311
}
315312

316313
case class DirectorySourcePath(dir: File) extends JFileDirectoryLookup[SourceFileEntryImpl] with NoClassPaths {
@@ -334,5 +331,5 @@ case class DirectorySourcePath(dir: File) extends JFileDirectoryLookup[SourceFil
334331
}
335332
}
336333

337-
private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage)
334+
private[nsc] def sources(inPackage: PackageName): Seq[SourceFileEntry] = files(inPackage)
338335
}

src/compiler/scala/tools/nsc/classpath/VirtualDirectoryClassPath.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
4545
Option(AbstractFileClassLoader.lookupPath(dir)(relativePath split '/', directory = false))
4646
}
4747

48-
private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
48+
private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
4949

5050
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
5151
protected def isMatchingFile(f: AbstractFile): Boolean = f.isClass

src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
5959

6060
override def findClassFile(className: String): Option[AbstractFile] = {
6161
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
62-
file(pkg, simpleClassName + ".class").map(_.file)
62+
file(PackageName(pkg), simpleClassName + ".class").map(_.file)
6363
}
6464
// This method is performance sensitive as it is used by SBT's ExtractDependencies phase.
6565
override def findClass(className: String): Option[ClassRepresentation] = {
6666
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
67-
file(pkg, simpleClassName + ".class")
67+
file(PackageName(pkg), simpleClassName + ".class")
6868
}
6969

70-
override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
70+
override private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
7171

7272
override protected def createFileEntry(file: FileZipArchive#Entry): ClassFileEntryImpl = ClassFileEntryImpl(file)
7373
override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isClass
@@ -83,7 +83,7 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
8383
private case class ManifestResourcesClassPath(file: ManifestResources) extends ClassPath with NoSourcePaths with Closeable {
8484
override def findClassFile(className: String): Option[AbstractFile] = {
8585
val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
86-
classes(pkg).find(_.name == simpleClassName).map(_.file)
86+
classes(PackageName(pkg)).find(_.name == simpleClassName).map(_.file)
8787
}
8888

8989
override def asClassPathStrings: Seq[String] = Seq(file.path)
@@ -135,22 +135,21 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
135135
packages
136136
}
137137

138-
override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = cachedPackages.get(inPackage) match {
138+
override private[nsc] def packages(inPackage: PackageName): Seq[PackageEntry] = cachedPackages.get(inPackage.dottedString) match {
139139
case None => Seq.empty
140140
case Some(PackageFileInfo(_, subpackages)) =>
141-
val prefix = PackageNameUtils.packagePrefix(inPackage)
142-
subpackages.map(packageFile => PackageEntryImpl(prefix + packageFile.name))
141+
subpackages.map(packageFile => PackageEntryImpl(inPackage.entryName(packageFile.name)))
143142
}
144143

145-
override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = cachedPackages.get(inPackage) match {
144+
override private[nsc] def classes(inPackage: PackageName): Seq[ClassFileEntry] = cachedPackages.get(inPackage.dottedString) match {
146145
case None => Seq.empty
147146
case Some(PackageFileInfo(pkg, _)) =>
148147
(for (file <- pkg if file.isClass) yield ClassFileEntryImpl(file))(collection.breakOut)
149148
}
150149

151150

152-
override private[nsc] def hasPackage(pkg: String) = cachedPackages.contains(pkg)
153-
override private[nsc] def list(inPackage: String): ClassPathEntries = ClassPathEntries(packages(inPackage), classes(inPackage))
151+
override private[nsc] def hasPackage(pkg: PackageName) = cachedPackages.contains(pkg.dottedString)
152+
override private[nsc] def list(inPackage: PackageName): ClassPathEntries = ClassPathEntries(packages(inPackage), classes(inPackage))
154153
}
155154

156155
private object ManifestResourcesClassPath {
@@ -183,7 +182,7 @@ object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory {
183182

184183
override def asSourcePathString: String = asClassPathString
185184

186-
override private[nsc] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage)
185+
override private[nsc] def sources(inPackage: PackageName): Seq[SourceFileEntry] = files(inPackage)
187186

188187
override protected def createFileEntry(file: FileZipArchive#Entry): SourceFileEntryImpl = SourceFileEntryImpl(file)
189188
override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isScalaOrJavaSource

src/compiler/scala/tools/nsc/classpath/ZipArchiveFileLookup.scala

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,54 +36,49 @@ trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends ClassPa
3636
private val archive = new FileZipArchive(zipFile, release)
3737
override def close(): Unit = archive.close()
3838

39-
override private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
40-
val prefix = PackageNameUtils.packagePrefix(inPackage)
39+
override private[nsc] def packages(inPackage: PackageName): Seq[PackageEntry] = {
4140
for {
4241
dirEntry <- findDirEntry(inPackage).toSeq
4342
entry <- dirEntry.iterator if entry.isPackage
44-
} yield PackageEntryImpl(prefix + entry.name)
43+
} yield PackageEntryImpl(inPackage.entryName(entry.name))
4544
}
4645

47-
protected def files(inPackage: String): Seq[FileEntryType] =
46+
protected def files(inPackage: PackageName): Seq[FileEntryType] =
4847
for {
4948
dirEntry <- findDirEntry(inPackage).toSeq
5049
entry <- dirEntry.iterator if isRequiredFileType(entry)
5150
} yield createFileEntry(entry)
5251

53-
protected def file(inPackage: String, name: String): Option[FileEntryType] =
52+
protected def file(inPackage: PackageName, name: String): Option[FileEntryType] =
5453
for {
5554
dirEntry <- findDirEntry(inPackage)
5655
entry <- Option(dirEntry.lookupName(name, directory = false))
5756
if isRequiredFileType(entry)
5857
} yield createFileEntry(entry)
5958

60-
override private[nsc] def hasPackage(pkg: String) = findDirEntry(pkg).isDefined
61-
override private[nsc] def list(inPackage: String): ClassPathEntries = {
59+
override private[nsc] def hasPackage(pkg: PackageName) = findDirEntry(pkg).isDefined
60+
override private[nsc] def list(inPackage: PackageName): ClassPathEntries = {
6261
val foundDirEntry = findDirEntry(inPackage)
6362

6463
foundDirEntry map { dirEntry =>
6564
val pkgBuf = collection.mutable.ArrayBuffer.empty[PackageEntry]
6665
val fileBuf = collection.mutable.ArrayBuffer.empty[FileEntryType]
67-
val prefix = PackageNameUtils.packagePrefix(inPackage)
68-
6966
for (entry <- dirEntry.iterator) {
7067
if (entry.isPackage)
71-
pkgBuf += PackageEntryImpl(prefix + entry.name)
68+
pkgBuf += PackageEntryImpl(inPackage.entryName(entry.name))
7269
else if (isRequiredFileType(entry))
7370
fileBuf += createFileEntry(entry)
7471
}
7572
ClassPathEntries(pkgBuf, fileBuf)
7673
} getOrElse ClassPathEntries.empty
7774
}
7875

79-
private def findDirEntry(pkg: String): Option[archive.DirEntry] = {
80-
archive.allDirs.get(dottedToPath(pkg))
76+
private def findDirEntry(pkg: PackageName): Option[archive.DirEntry] = {
77+
archive.allDirs.get(pkg.dirPathTrailingSlash)
8178
}
8279

83-
private def dottedToPath(dotted: String): String = {
84-
dotted.replace('.', '/') + "/"
85-
}
8680

8781
protected def createFileEntry(file: FileZipArchive#Entry): FileEntryType
8882
protected def isRequiredFileType(file: AbstractFile): Boolean
8983
}
84+

0 commit comments

Comments
 (0)