From b627de8553e3e47efdc9ee7c2db59fbaf142e576 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 8 Feb 2010 23:09:49 +0000 Subject: Until now directories on the classpath were not... Until now directories on the classpath were not considered for repl completion because of the risk of accidentally traversing large chunks of the filesystem (e.g. "." in the path, run from /). Some low hanging fruit was available - at least SCALA_HOME can be considered safe, and then we get the scala classes. The main impact of this is that completion now works for the built-in classes when you run build/quick/bin/scala. Review by community. --- .../tools/nsc/interpreter/PackageCompletion.scala | 83 +++++++++++++++------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala index 5ee8877124..ff5c4b3d8b 100644 --- a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala @@ -9,6 +9,8 @@ package interpreter import java.net.URL import java.lang.reflect import java.util.concurrent.ConcurrentHashMap +import io.{ Path, Directory, File, Streamable } +import scala.tools.util.PathResolver.Defaults.scalaHomeDir import scala.concurrent.DelayedLazyVal import scala.reflect.NameTransformer.{ decode, encode } import PackageCompletion._ @@ -51,7 +53,7 @@ class PackageCompletion(classpath: List[URL]) extends CompletionAware { override def follow(segment: String): Option[CompletionAware] = { PackageCompletion.this.follow(root + "." + segment) orElse { - for (CompletionInfo(`segment`, className, _) <- infos) { + for (CompletionInfo(`segment`, className) <- infos) { return Some(new StaticCompletion(className)) } @@ -63,9 +65,7 @@ class PackageCompletion(classpath: List[URL]) extends CompletionAware { } object PackageCompletion { - import java.io.File import java.util.jar.{ JarEntry, JarFile } - import scala.tools.nsc.io.Streamable val EXPAND_SEPARATOR_STRING = "$$" val ANON_CLASS_NAME = "$anon" @@ -83,24 +83,44 @@ object PackageCompletion { private def enumToListInternal[T](e: java.util.Enumeration[T], xs: List[T]): List[T] = if (e == null || !e.hasMoreElements) xs else enumToListInternal(e, e.nextElement :: xs) - def getClassFiles(path: String): List[String] = { - def exists(path: String) = { new File(path) exists } - if (!exists(path)) Nil else { - (enumToList(new JarFile(path).entries) map (_.getName)) - . partialMap { case x: String if x endsWith ".class" => x dropRight 6 } - . filterNot { ignoreClassName } + private def isClass(s: String) = s endsWith ".class" + private def processNames(xs: List[String]) = xs map (_ dropRight 6) filterNot ignoreClassName distinct + + def getDirClassFiles(dir: Directory): List[String] = + processNames(dir.deepList() map (dir relativize _ path) filter isClass toList) + + def getJarClassFiles(jar: File): List[String] = + if (!jar.exists) Nil + else processNames(enumToList(new JarFile(jar.path).entries) map (_.getName) filter isClass) + + object CompletionInfo { + def unapply(that: Any) = that match { + case x: CompletionInfo => Some((x.visibleName, x.className)) + case _ => None } } - case class CompletionInfo(visibleName: String, className: String, jar: String) { - lazy val jarfile = new JarFile(jar) - lazy val entry = jarfile getEntry className + abstract class CompletionInfo { + def visibleName: String + def className: String + def getBytes(): Array[Byte] override def hashCode = visibleName.hashCode override def equals(other: Any) = other match { case x: CompletionInfo => visibleName == x.visibleName case _ => false } + } + + case class DirCompletionInfo(visibleName: String, className: String, dir: Directory) extends CompletionInfo { + lazy val file = dir / File(className) + + def getBytes(): Array[Byte] = try file.toByteArray() catch { case _: Exception => Array() } + } + + case class JarCompletionInfo(visibleName: String, className: String, jar: File) extends CompletionInfo { + lazy val jarfile = new JarFile(jar.path) + lazy val entry = jarfile getEntry className def getBytes(): Array[Byte] = { if (entry == null) Array() else { @@ -112,8 +132,16 @@ object PackageCompletion { // all the dotted path to classfiles we can find by poking through the jars def getDottedPaths(map: ConcurrentHashMap[String, List[CompletionInfo]], classpath: List[URL]): Unit = { - val cp = classpath map (_.getPath) - val jars = cp.distinct filter (_ endsWith ".jar") + val cp = classpath.distinct map (x => Path(x.getPath)) + val jars = cp filter (_ hasExtension "jar") map (_.toFile) + + /** If we process all dirs uncritically, someone who has '.' in their classpath and + * runs scala from the filesystem root directory will induce a traversal of their + * entire filesystem. We could apply some heuristics to avoid this, but for now we + * will look only in the scalaHome directories, which is most of what we want. + */ + def isUnderScalaHome(d: Directory) = d.parents exists (_ == scalaHomeDir) + val dirs = cp partialMap { case x: Directory => x } filter isUnderScalaHome // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C) // and scala.Range$BigInt needs to go scala -> Range -> BigInt @@ -134,21 +162,26 @@ object PackageCompletion { } } - def oneJar(jar: String): Unit = { - val classfiles = getClassFiles(jar) + def addToMap(key: String, info: CompletionInfo) = { + if (map containsKey key) { + val vs = map.get(key) + if (vs contains info) () + else map.put(key, info :: vs) + } + else map.put(key, List(info)) + } - for (cl <- classfiles.distinct ; (k, _v) <- subpaths(cl)) { - val v = CompletionInfo(_v, cl, jar) + def oneDir(dir: Directory) { + for (cl <- getDirClassFiles(dir) ; (k, v) <- subpaths(cl)) + addToMap(k, DirCompletionInfo(v, cl, dir)) + } - if (map containsKey k) { - val vs = map.get(k) - if (vs contains v) () - else map.put(k, v :: vs) - } - else map.put(k, List(v)) - } + def oneJar(jar: File) { + for (cl <- getJarClassFiles(jar) ; (k, v) <- subpaths(cl)) + addToMap(k, JarCompletionInfo(v, cl, jar)) } jars foreach oneJar + dirs foreach oneDir } } \ No newline at end of file -- cgit v1.2.3