diff options
Diffstat (limited to 'compiler/src/dotty/tools/io/ClassPath.scala')
-rw-r--r-- | compiler/src/dotty/tools/io/ClassPath.scala | 425 |
1 files changed, 80 insertions, 345 deletions
diff --git a/compiler/src/dotty/tools/io/ClassPath.scala b/compiler/src/dotty/tools/io/ClassPath.scala index 5e77c1b61..b4cc426cf 100644 --- a/compiler/src/dotty/tools/io/ClassPath.scala +++ b/compiler/src/dotty/tools/io/ClassPath.scala @@ -1,56 +1,89 @@ /* NSC -- new Scala compiler - * Copyright 2006-2012 LAMP/EPFL + * Copyright 2006-2013 LAMP/EPFL * @author Martin Odersky */ + package dotty.tools package io +import java.net.MalformedURLException import java.net.URL -import scala.collection.{ mutable, immutable } -import dotc.core.Decorators.StringDecorator +import java.util.regex.PatternSyntaxException + import File.pathSeparator -import java.net.MalformedURLException import Jar.isJarOrZip -import ClassPath._ -import scala.Option.option2Iterable -import scala.reflect.io.Path.string2path -import language.postfixOps - -/** <p> - * This module provides star expansion of '-classpath' option arguments, behaves the same as - * java, see [http://java.sun.com/javase/6/docs/technotes/tools/windows/classpath.html] - * </p> - * - * @author Stepan Koltsov - */ + +/** + * A representation of the compiler's class- or sourcepath. + */ +trait ClassPath { + import dotty.tools.dotc.classpath._ + def asURLs: Seq[URL] + + /** Empty string represents root package */ + private[dotty] def packages(inPackage: String): Seq[PackageEntry] + private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] + private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] + + /** Allows to get entries for packages and classes merged with sources possibly in one pass. */ + private[dotty] def list(inPackage: String): ClassPathEntries + + /** + * It returns both classes from class file and source files (as our base ClassRepresentation). + * So note that it's not so strictly related to findClassFile. + */ + def findClass(className: String): Option[ClassRepresentation] = { + // A default implementation which should be overridden, if we can create the more efficient + // 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) + + foundClassFromClassFiles orElse findClassInSources + } + def findClassFile(className: String): Option[AbstractFile] + + def asClassPathStrings: Seq[String] + + /** The whole classpath in the form of one String. + */ + def asClassPathString: String = ClassPath.join(asClassPathStrings: _*) + // for compatibility purposes + @deprecated("use asClassPathString instead of this one", "2.11.5") + def asClasspathString: String = asClassPathString + + /** The whole sourcepath in the form of one String. + */ + def asSourcePathString: String +} + object ClassPath { + val RootPackage = "" /** Expand single path entry */ private def expandS(pattern: String): List[String] = { val wildSuffix = File.separator + "*" - /** Get all subdirectories, jars, zips out of a directory. */ - def lsDir(dir: Directory, filt: String => Boolean = _ => true) = { - val files = synchronized(dir.list) - files filter (x => filt(x.name) && (x.isDirectory || isJarOrZip(x))) map (_.path) toList - } - - def basedir(s: String) = - if (s contains File.separator) s.substring(0, s.lastIndexOf(File.separator)) - else "." + /* Get all subdirectories, jars, zips out of a directory. */ + def lsDir(dir: Directory, filt: String => Boolean = _ => true) = + dir.list.filter(x => filt(x.name) && (x.isDirectory || isJarOrZip(x))).map(_.path).toList if (pattern == "*") lsDir(Directory(".")) else if (pattern endsWith wildSuffix) lsDir(Directory(pattern dropRight 2)) else if (pattern contains '*') { - val regexp = ("^%s$" format pattern.replaceAll("""\*""", """.*""")).r - lsDir(Directory(pattern).parent, regexp findFirstIn _ isDefined) + try { + val regexp = ("^" + pattern.replaceAllLiterally("""\*""", """.*""") + "$").r + lsDir(Directory(pattern).parent, regexp.findFirstIn(_).isDefined) + } + catch { case _: PatternSyntaxException => List(pattern) } } else List(pattern) } /** Split classpath using platform-dependent path separator */ - def split(path: String): List[String] = (path split pathSeparator).toList filterNot (_ == "") distinct + def split(path: String): List[String] = (path split pathSeparator).toList.filterNot(_ == "").distinct /** Join classpath using platform-dependent path separator */ def join(paths: String*): String = paths filterNot (_ == "") mkString pathSeparator @@ -58,22 +91,6 @@ object ClassPath { /** Split the classpath, apply a transformation function, and reassemble it. */ def map(cp: String, f: String => String): String = join(split(cp) map f: _*) - /** Split the classpath, filter according to predicate, and reassemble. */ - def filter(cp: String, p: String => Boolean): String = join(split(cp) filter p: _*) - - /** Split the classpath and map them into Paths */ - def toPaths(cp: String): List[Path] = split(cp) map (x => Path(x).toAbsolute) - - /** Make all classpath components absolute. */ - def makeAbsolute(cp: String): String = fromPaths(toPaths(cp): _*) - - /** Join the paths as a classpath */ - def fromPaths(paths: Path*): String = join(paths map (_.path): _*) - def fromURLs(urls: URL*): String = fromPaths(urls map (x => Path(x.getPath)) : _*) - - /** Split the classpath and map them into URLs */ - def toURLs(cp: String): List[URL] = toPaths(cp) map (_.toURL) - /** Expand path and possibly expanding stars */ def expandPath(path: String, expandStar: Boolean = true): List[String] = if (expandStar) split(path) flatMap expandS @@ -83,9 +100,10 @@ object ClassPath { def expandDir(extdir: String): List[String] = { AbstractFile getDirectory extdir match { case null => Nil - case dir => dir filter (_.isClassContainer) map (x => new java.io.File(dir.file, x.name) getPath) toList + case dir => dir.filter(_.isClassContainer).map(x => new java.io.File(dir.file, x.name).getPath).toList } } + /** Expand manifest jar classpath entries: these are either urls, or paths * relative to the location of the jar. */ @@ -99,317 +117,34 @@ object ClassPath { ) } - /** A useful name filter. */ - def isTraitImplementation(name: String) = name endsWith "$class.class" - def specToURL(spec: String): Option[URL] = try Some(new URL(spec)) catch { case _: MalformedURLException => None } - /** A class modeling aspects of a ClassPath which should be - * propagated to any classpaths it creates. - */ - abstract class ClassPathContext { - /** A filter which can be used to exclude entities from the classpath - * based on their name. - */ - def isValidName(name: String): Boolean = true - - /** From the representation to its identifier. - */ - def toBinaryName(rep: AbstractFile): String - - /** Create a new classpath based on the abstract file. - */ - def newClassPath(file: AbstractFile): ClassPath - - /** Creators for sub classpaths which preserve this context. - */ - def sourcesInPath(path: String): List[ClassPath] = - for (file <- expandPath(path, false) ; dir <- Option(AbstractFile getDirectory file)) yield - new SourcePath(dir, this) - - def contentsOfDirsInPath(path: String): List[ClassPath] = - for (dir <- expandPath(path, false) ; name <- expandDir(dir) ; entry <- Option(AbstractFile getDirectory name)) yield - newClassPath(entry) - - def classesAtAllURLS(path: String): List[ClassPath] = - (path split " ").toList flatMap classesAtURL - - def classesAtURL(spec: String) = - for (url <- specToURL(spec).toList ; location <- Option(AbstractFile getURL url)) yield - newClassPath(location) - - def classesInExpandedPath(path: String): IndexedSeq[ClassPath] = - classesInPathImpl(path, true).toIndexedSeq - - def classesInPath(path: String) = classesInPathImpl(path, false) - - // Internal - private def classesInPathImpl(path: String, expand: Boolean) = - for (file <- expandPath(path, expand) ; dir <- Option(AbstractFile getDirectory file)) yield - newClassPath(dir) - } - - class JavaContext extends ClassPathContext { - def toBinaryName(rep: AbstractFile) = { - val name = rep.name - assert(endsClass(name), name) - name.substring(0, name.length - 6) - } - def newClassPath(dir: AbstractFile) = new DirectoryClassPath(dir, this) - } - - object DefaultJavaContext extends JavaContext { - override def isValidName(name: String) = !isTraitImplementation(name) + def manifests: List[java.net.URL] = { + import scala.collection.JavaConverters._ + val resources = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF") + resources.asScala.filter(_.getProtocol == "jar").toList } - private def endsClass(s: String) = s.length > 6 && s.substring(s.length - 6) == ".class" - private def endsScala(s: String) = s.length > 6 && s.substring(s.length - 6) == ".scala" - private def endsJava(s: String) = s.length > 5 && s.substring(s.length - 5) == ".java" - - /** From the source file to its identifier. - */ - def toSourceName(f: AbstractFile): String = { - val name = f.name + @deprecated("shim for sbt's compiler interface", since = "2.12.0") + sealed abstract class ClassPathContext - if (endsScala(name)) name.substring(0, name.length - 6) - else if (endsJava(name)) name.substring(0, name.length - 5) - else throw new FatalError("Unexpected source file ending: " + name) - } + @deprecated("shim for sbt's compiler interface", since = "2.12.0") + sealed abstract class JavaContext } -/** - * Represents a package which contains classes and other packages - */ -abstract class ClassPath { - type AnyClassRep = ClassPath#ClassRep - - /** - * The short name of the package (without prefix) - */ +trait ClassRepresentation { def name: String - - /** - * A String representing the origin of this classpath element, if known. - * For example, the path of the directory or jar. - */ - def origin: Option[String] = None - - /** A list of URLs representing this classpath. - */ - def asURLs: List[URL] - - /** The whole classpath in the form of one String. - */ - def asClasspathString: String - - /** Info which should be propagated to any sub-classpaths. - */ - def context: ClassPathContext - - /** Lists of entities. - */ - def classes: IndexedSeq[AnyClassRep] - def packages: IndexedSeq[ClassPath] - def sourcepaths: IndexedSeq[AbstractFile] - - /** - * Represents classes which can be loaded with a ClassfileLoader - * and / or a SourcefileLoader. - */ - case class ClassRep(binary: Option[AbstractFile], source: Option[AbstractFile]) { - def name: String = binary match { - case Some(x) => context.toBinaryName(x) - case _ => - assert(source.isDefined) - toSourceName(source.get) - } - } - - /** Filters for assessing validity of various entities. - */ - def validClassFile(name: String) = endsClass(name) && context.isValidName(name) - def validPackage(name: String) = (name != "META-INF") && (name != "") && (name.charAt(0) != '.') - def validSourceFile(name: String) = endsScala(name) || endsJava(name) - - /** - * Find a ClassRep given a class name of the form "package.subpackage.ClassName". - * Does not support nested classes on .NET - */ - def findClass(name: String): Option[AnyClassRep] = - name.splitWhere(_ == '.', doDropIndex = true) match { - case Some((pkg, rest)) => - packages find (_.name == pkg) flatMap (_ findClass rest) - case _ => - classes find (_.name == name) - } - - def findBinaryFile(name: String): Option[AbstractFile] = - findClass(name).flatMap(_.binary) - - 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() + def binary: Option[AbstractFile] + def source: Option[AbstractFile] } -/** - * A Classpath containing source files - */ -class SourcePath(dir: AbstractFile, val context: ClassPathContext) extends ClassPath { - def name = dir.name - override def origin = dir.underlyingSource map (_.path) - def asURLs = if (dir.file == null) Nil else List(dir.toURL) - def asClasspathString = dir.path - val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq(dir) - - private def traverse() = { - val classBuf = immutable.Vector.newBuilder[ClassRep] - val packageBuf = immutable.Vector.newBuilder[SourcePath] - dir foreach { f => - if (!f.isDirectory && validSourceFile(f.name)) - classBuf += ClassRep(None, Some(f)) - else if (f.isDirectory && validPackage(f.name)) - packageBuf += new SourcePath(f, context) - } - (packageBuf.result, classBuf.result) - } +@deprecated("shim for sbt's compiler interface", since = "2.12.0") +sealed abstract class DirectoryClassPath - lazy val (packages, classes) = traverse() - override def toString() = "sourcepath: " + dir.toString() -} +@deprecated("shim for sbt's compiler interface", since = "2.12.0") +sealed abstract class MergedClassPath -/** - * A directory (or a .jar file) containing classfiles and packages - */ -class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext) extends ClassPath { - def name = dir.name - override def origin = dir.underlyingSource map (_.path) - def asURLs = if (dir.file == null) Nil else List(dir.toURL) - def asClasspathString = dir.path - val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq() - - // 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)) - classBuf += ClassRep(Some(f), None) - else if (f.isDirectory && validPackage(f.name)) - packageBuf += new DirectoryClassPath(f, context) - } - (packageBuf.result, classBuf.result) - } - - lazy val (packages, classes) = traverse() - override def toString() = "directory classpath: " + origin.getOrElse("?") -} - -class DeltaClassPath(original: MergedClassPath, subst: Map[ClassPath, ClassPath]) -extends MergedClassPath(original.entries map (e => subst getOrElse (e, e)), original.context) { - // not sure we should require that here. Commented out for now. - // require(subst.keySet subsetOf original.entries.toSet) - // We might add specialized operations for computing classes packages here. Not sure it's worth it. -} - -/** - * A classpath unifying multiple class- and sourcepath entries. - */ -class MergedClassPath( - val entries: IndexedSeq[ClassPath], - val context: ClassPathContext) -extends ClassPath { - def this(entries: TraversableOnce[ClassPath], context: ClassPathContext) = - this(entries.toIndexedSeq, context) - - def name = entries.head.name - def asURLs = (entries flatMap (_.asURLs)).toList - lazy val sourcepaths: IndexedSeq[AbstractFile] = entries flatMap (_.sourcepaths) - - override def origin = Some(entries map (x => x.origin getOrElse x.name) mkString ("Merged(", ", ", ")")) - override def asClasspathString: String = join(entries map (_.asClasspathString) : _*) - - lazy val classes: IndexedSeq[AnyClassRep] = { - var count = 0 - val indices = mutable.AnyRefMap[String, Int]() - val cls = new mutable.ArrayBuffer[AnyClassRep](1024) - - for (e <- entries; c <- e.classes) { - val name = c.name - if (indices contains name) { - val idx = indices(name) - val existing = cls(idx) - - if (existing.binary.isEmpty && c.binary.isDefined) - cls(idx) = existing.copy(binary = c.binary) - if (existing.source.isEmpty && c.source.isDefined) - cls(idx) = existing.copy(source = c.source) - } - else { - indices(name) = count - cls += c - count += 1 - } - } - cls.toIndexedSeq - } - - lazy val packages: IndexedSeq[ClassPath] = { - var count = 0 - val indices = mutable.AnyRefMap[String, Int]() - val pkg = new mutable.ArrayBuffer[ClassPath](256) - - for (e <- entries; p <- e.packages) { - val name = p.name - if (indices contains name) { - val idx = indices(name) - pkg(idx) = addPackage(pkg(idx), p) - } - else { - indices(name) = count - pkg += p - count += 1 - } - } - pkg.toIndexedSeq - } - - private def addPackage(to: ClassPath, pkg: ClassPath) = { - val newEntries: IndexedSeq[ClassPath] = to match { - case cp: MergedClassPath => cp.entries :+ pkg - case _ => IndexedSeq(to, pkg) - } - new MergedClassPath(newEntries, context) - } - def show(): Unit = { - println("ClassPath %s has %d entries and results in:\n".format(name, entries.size)) - asClasspathString split ':' foreach (x => println(" " + x)) - } - override def toString() = "merged classpath " + entries.mkString("(", "\n", ")") -} - -/** - * The classpath when compiling with target:jvm. Binary files (classfiles) are represented - * as AbstractFile. nsc.io.ZipArchive is used to view zip/jar archives as directories. - */ -class JavaClassPath( - containers: IndexedSeq[ClassPath], - context: JavaContext) -extends MergedClassPath(containers, context) { } - -object JavaClassPath { - def fromURLs(urls: Seq[URL], context: JavaContext): JavaClassPath = { - val containers = { - for (url <- urls ; f = AbstractFile getURL url ; if f != null) yield - new DirectoryClassPath(f, context) - } - new JavaClassPath(containers.toIndexedSeq, context) - } - def fromURLs(urls: Seq[URL]): JavaClassPath = - fromURLs(urls, ClassPath.DefaultJavaContext) -} +@deprecated("shim for sbt's compiler interface", since = "2.12.0") +sealed abstract class JavaClassPath |