aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala')
-rw-r--r--compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala176
1 files changed, 176 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala
new file mode 100644
index 000000000..5210c699e
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2014 Contributor. All rights reserved.
+ */
+package dotty.tools.dotc.classpath
+
+import java.io.File
+import java.net.URL
+import scala.annotation.tailrec
+import scala.reflect.io.{AbstractFile, FileZipArchive, ManifestResources}
+import dotty.tools.io.ClassPath
+import dotty.tools.dotc.config.Settings
+import dotty.tools.dotc.core.Contexts.Context
+import FileUtils._
+
+/**
+ * A trait providing an optional cache for classpath entries obtained from zip and jar files.
+ * It's possible to create such a cache assuming that entries in such files won't change (at
+ * least will be the same each time we'll load classpath during the lifetime of JVM process)
+ * - unlike class and source files in directories, which can be modified and recompiled.
+ * It allows us to e.g. reduce significantly memory used by PresentationCompilers in Scala IDE
+ * when there are a lot of projects having a lot of common dependencies.
+ */
+sealed trait ZipAndJarFileLookupFactory {
+ private val cache = collection.mutable.Map.empty[AbstractFile, ClassPath]
+
+ def create(zipFile: AbstractFile)(implicit ctx: Context): ClassPath = {
+ if (ctx.settings.YdisableFlatCpCaching.value) createForZipFile(zipFile)
+ else createUsingCache(zipFile)
+ }
+
+ protected def createForZipFile(zipFile: AbstractFile): ClassPath
+
+ private def createUsingCache(zipFile: AbstractFile)(implicit ctx: Context): ClassPath = cache.synchronized {
+ def newClassPathInstance = {
+ if (ctx.settings.verbose.value || ctx.settings.Ylogcp.value)
+ println(s"$zipFile is not yet in the classpath cache")
+ createForZipFile(zipFile)
+ }
+ cache.getOrElseUpdate(zipFile, newClassPathInstance)
+ }
+}
+
+/**
+ * Manages creation of classpath for class files placed in zip and jar files.
+ * It should be the only way of creating them as it provides caching.
+ */
+object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
+ private case class ZipArchiveClassPath(zipFile: File)
+ extends ZipArchiveFileLookup[ClassFileEntryImpl]
+ with NoSourcePaths {
+
+ override def findClassFile(className: String): Option[AbstractFile] = {
+ val (pkg, simpleClassName) = PackageNameUtils.separatePkgAndClassNames(className)
+ classes(pkg).find(_.name == simpleClassName).map(_.file)
+ }
+
+ override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage)
+
+ override protected def createFileEntry(file: FileZipArchive#Entry): ClassFileEntryImpl = ClassFileEntryImpl(file)
+ override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isClass
+ }
+
+ /**
+ * This type of classpath is closely related to the support for JSR-223.
+ * Its usage can be observed e.g. when running:
+ * jrunscript -classpath scala-compiler.jar;scala-reflect.jar;scala-library.jar -l scala
+ * with a particularly prepared scala-library.jar. It should have all classes listed in the manifest like e.g. this entry:
+ * Name: scala/Function2$mcFJD$sp.class
+ */
+ 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)
+ }
+
+ override def asClassPathStrings: Seq[String] = Seq(file.path)
+
+ override def asURLs: Seq[URL] = file.toURLs()
+
+ import ManifestResourcesClassPath.PackageFileInfo
+ import ManifestResourcesClassPath.PackageInfo
+
+ /**
+ * A cache mapping package name to abstract file for package directory and subpackages of given package.
+ *
+ * ManifestResources can iterate through the collections of entries from e.g. remote jar file.
+ * We can't just specify the path to the concrete directory etc. so we can't just 'jump' into
+ * given package, when it's needed. On the other hand we can iterate over entries to get
+ * AbstractFiles, iterate over entries of these files etc.
+ *
+ * Instead of traversing a tree of AbstractFiles once and caching all entries or traversing each time,
+ * when we need subpackages of a given package or its classes, we traverse once and cache only packages.
+ * Classes for given package can be then easily loaded when they are needed.
+ */
+ private lazy val cachedPackages: collection.mutable.HashMap[String, PackageFileInfo] = {
+ val packages = collection.mutable.HashMap[String, PackageFileInfo]()
+
+ def getSubpackages(dir: AbstractFile): List[AbstractFile] =
+ (for (file <- dir if file.isPackage) yield file)(collection.breakOut)
+
+ @tailrec
+ def traverse(packagePrefix: String,
+ filesForPrefix: List[AbstractFile],
+ subpackagesQueue: collection.mutable.Queue[PackageInfo]): Unit = filesForPrefix match {
+ case pkgFile :: remainingFiles =>
+ val subpackages = getSubpackages(pkgFile)
+ val fullPkgName = packagePrefix + pkgFile.name
+ packages.put(fullPkgName, PackageFileInfo(pkgFile, subpackages))
+ val newPackagePrefix = fullPkgName + "."
+ subpackagesQueue.enqueue(PackageInfo(newPackagePrefix, subpackages))
+ traverse(packagePrefix, remainingFiles, subpackagesQueue)
+ case Nil if subpackagesQueue.nonEmpty =>
+ val PackageInfo(packagePrefix, filesForPrefix) = subpackagesQueue.dequeue()
+ traverse(packagePrefix, filesForPrefix, subpackagesQueue)
+ case _ =>
+ }
+
+ val subpackages = getSubpackages(file)
+ packages.put(ClassPath.RootPackage, PackageFileInfo(file, subpackages))
+ traverse(ClassPath.RootPackage, subpackages, collection.mutable.Queue())
+ packages
+ }
+
+ override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = cachedPackages.get(inPackage) match {
+ case None => Seq.empty
+ case Some(PackageFileInfo(_, subpackages)) =>
+ val prefix = PackageNameUtils.packagePrefix(inPackage)
+ subpackages.map(packageFile => PackageEntryImpl(prefix + packageFile.name))
+ }
+
+ override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = cachedPackages.get(inPackage) match {
+ case None => Seq.empty
+ case Some(PackageFileInfo(pkg, _)) =>
+ (for (file <- pkg if file.isClass) yield ClassFileEntryImpl(file))(collection.breakOut)
+ }
+
+ override private[dotty] def list(inPackage: String): ClassPathEntries = ClassPathEntries(packages(inPackage), classes(inPackage))
+ }
+
+ private object ManifestResourcesClassPath {
+ case class PackageFileInfo(packageFile: AbstractFile, subpackages: Seq[AbstractFile])
+ case class PackageInfo(packageName: String, subpackages: List[AbstractFile])
+ }
+
+ override protected def createForZipFile(zipFile: AbstractFile): ClassPath =
+ if (zipFile.file == null) createWithoutUnderlyingFile(zipFile)
+ else ZipArchiveClassPath(zipFile.file)
+
+ private def createWithoutUnderlyingFile(zipFile: AbstractFile) = zipFile match {
+ case manifestRes: ManifestResources =>
+ ManifestResourcesClassPath(manifestRes)
+ case _ =>
+ val errorMsg = s"Abstract files which don't have an underlying file and are not ManifestResources are not supported. There was $zipFile"
+ throw new IllegalArgumentException(errorMsg)
+ }
+}
+
+/**
+ * Manages creation of classpath for source files placed in zip and jar files.
+ * It should be the only way of creating them as it provides caching.
+ */
+object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory {
+ private case class ZipArchiveSourcePath(zipFile: File)
+ extends ZipArchiveFileLookup[SourceFileEntryImpl]
+ with NoClassPaths {
+
+ override def asSourcePathString: String = asClassPathString
+
+ override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = files(inPackage)
+
+ override protected def createFileEntry(file: FileZipArchive#Entry): SourceFileEntryImpl = SourceFileEntryImpl(file)
+ override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isScalaOrJavaSource
+ }
+
+ override protected def createForZipFile(zipFile: AbstractFile): ClassPath = ZipArchiveSourcePath(zipFile.file)
+}