From 964a197cd90e561d05c9d725cc13895f18b6a6d0 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sat, 4 Oct 2014 12:33:11 -0700 Subject: SI-8843 AbsFileCL acts like a CL Let the AbstractFileClassLoader override just the usual suspects. Normal delegation behavior should ensue. That's instead of overriding `getResourceAsStream`, which was intended that "The repl classloader now works more like you'd expect a classloader to." (Workaround for "Don't know how to construct an URL for something which exists only in memory.") Also override `findResources` so that `getResources` does the obvious thing, namely, return one iff `getResource` does. The translating class loader for REPL only special-cases `foo.class`: as a fallback, take `foo` as `$line42.$read$something$foo` and try that class file. That's the use case for "works like you'd expect it to." There was a previous fix to ensure `getResource` doesn't take a class name. The convenience behavior, that `classBytes` takes either a class name or a resource path ending in ".class", has been promoted to `ScalaClassLoader`. --- .../internal/util/AbstractFileClassLoader.scala | 66 +++++++++------------- .../reflect/internal/util/ScalaClassLoader.scala | 6 +- src/repl/scala/tools/nsc/interpreter/IMain.scala | 34 ++++++++--- 3 files changed, 56 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala index 10a8b4c812..30dcbc21ca 100644 --- a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala +++ b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala @@ -5,16 +5,16 @@ package scala package reflect.internal.util -import scala.reflect.io.AbstractFile +import scala.collection.{ mutable, immutable } +import scala.reflect.io.{ AbstractFile, Streamable } +import java.net.{ URL, URLConnection, URLStreamHandler } import java.security.cert.Certificate import java.security.{ ProtectionDomain, CodeSource } -import java.net.{ URL, URLConnection, URLStreamHandler } -import scala.collection.{ mutable, immutable } +import java.util.{ Collections => JCollections, Enumeration => JEnumeration } -/** - * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. +/** A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. * - * @author Lex Spoon + * @author Lex Spoon */ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) extends ClassLoader(parent) @@ -22,7 +22,7 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) { protected def classNameToPath(name: String): String = if (name endsWith ".class") name - else name.replace('.', '/') + ".class" + else s"${name.replace('.', '/')}.class" protected def findAbstractFile(name: String): AbstractFile = { var file: AbstractFile = root @@ -56,35 +56,25 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) file } - // parent delegation in JCL uses getResource; so either add parent.getResAsStream - // or implement findResource, which we do here as a study in scarlet (my complexion - // after looking at CLs and URLs) - override def findResource(name: String): URL = findAbstractFile(name) match { + override protected def findClass(name: String): Class[_] = { + val bytes = classBytes(name) + if (bytes.length == 0) + throw new ClassNotFoundException(name) + else + defineClass(name, bytes, 0, bytes.length, protectionDomain) + } + override protected def findResource(name: String): URL = findAbstractFile(name) match { case null => null - case file => new URL(null, "repldir:" + file.path, new URLStreamHandler { + case file => new URL(null, s"memory:${file.path}", new URLStreamHandler { override def openConnection(url: URL): URLConnection = new URLConnection(url) { - override def connect() { } + override def connect() = () override def getInputStream = file.input } }) } - - // this inverts delegation order: super.getResAsStr calls parent.getRes if we fail - override def getResourceAsStream(name: String) = findAbstractFile(name) match { - case null => super.getResourceAsStream(name) - case file => file.input - } - // ScalaClassLoader.classBytes uses getResAsStream, so we'll try again before delegating - override def classBytes(name: String): Array[Byte] = findAbstractFile(classNameToPath(name)) match { - case null => super.classBytes(name) - case file => file.toByteArray - } - override def findClass(name: String): Class[_] = { - val bytes = classBytes(name) - if (bytes.length == 0) - throw new ClassNotFoundException(name) - else - defineClass(name, bytes, 0, bytes.length, protectionDomain) + override protected def findResources(name: String): JEnumeration[URL] = findResource(name) match { + case null => JCollections.enumeration(JCollections.emptyList[URL]) //JCollections.emptyEnumeration[URL] + case url => JCollections.enumeration(JCollections.singleton(url)) } lazy val protectionDomain = { @@ -106,15 +96,13 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) throw new UnsupportedOperationException() } - override def getPackage(name: String): Package = { - findAbstractDir(name) match { - case null => super.getPackage(name) - case file => packages.getOrElseUpdate(name, { - val ctor = classOf[Package].getDeclaredConstructor(classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[URL], classOf[ClassLoader]) - ctor.setAccessible(true) - ctor.newInstance(name, null, null, null, null, null, null, null, this) - }) - } + override def getPackage(name: String): Package = findAbstractDir(name) match { + case null => super.getPackage(name) + case file => packages.getOrElseUpdate(name, { + val ctor = classOf[Package].getDeclaredConstructor(classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[URL], classOf[ClassLoader]) + ctor.setAccessible(true) + ctor.newInstance(name, null, null, null, null, null, null, null, this) + }) } override def getPackages(): Array[Package] = diff --git a/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala b/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala index 63ea6e2c49..41011f6c6b 100644 --- a/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala +++ b/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala @@ -53,8 +53,10 @@ trait ScalaClassLoader extends JClassLoader { } /** An InputStream representing the given class name, or null if not found. */ - def classAsStream(className: String) = - getResourceAsStream(className.replaceAll("""\.""", "/") + ".class") + def classAsStream(className: String) = getResourceAsStream { + if (className endsWith ".class") className + else s"${className.replace('.', '/')}.class" // classNameToPath + } /** Run the main method of a class to be loaded by this classloader */ def run(objectName: String, arguments: Seq[String]) { diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 6e30b73e0e..20b5a79aaa 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -295,22 +295,38 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set def originalPath(name: Name): String = typerOp path name def originalPath(sym: Symbol): String = typerOp path sym def flatPath(sym: Symbol): String = flatOp shift sym.javaClassName + def translatePath(path: String) = { val sym = if (path endsWith "$") symbolOfTerm(path.init) else symbolOfIdent(path) sym.toOption map flatPath } + + /** If path represents a class resource in the default package, + * see if the corresponding symbol has a class file that is a REPL artifact + * residing at a different resource path. Translate X.class to $line3/$read$$iw$$iw$X.class. + */ + def translateSimpleResource(path: String): Option[String] = { + if (!(path contains '/') && (path endsWith ".class")) { + val name = path stripSuffix ".class" + val sym = if (name endsWith "$") symbolOfTerm(name.init) else symbolOfIdent(name) + def pathOf(s: String) = s"${s.replace('.', '/')}.class" + sym.toOption map (s => pathOf(flatPath(s))) + } else { + None + } + } def translateEnclosingClass(n: String) = symbolOfTerm(n).enclClass.toOption map flatPath + /** If unable to find a resource foo.class, try taking foo as a symbol in scope + * and use its java class name as a resource to load. + * + * $intp.classLoader classBytes "Bippy" or $intp.classLoader getResource "Bippy.class" just work. + */ private class TranslatingClassLoader(parent: ClassLoader) extends util.AbstractFileClassLoader(replOutput.dir, parent) { - /** Overridden here to try translating a simple name to the generated - * class name if the original attempt fails. This method is used by - * getResourceAsStream as well as findClass. - */ - override protected def findAbstractFile(name: String): AbstractFile = - super.findAbstractFile(name) match { - case null if _initializeComplete => translatePath(name) map (super.findAbstractFile(_)) orNull - case file => file - } + override protected def findAbstractFile(name: String): AbstractFile = super.findAbstractFile(name) match { + case null if _initializeComplete => translateSimpleResource(name) map super.findAbstractFile orNull + case file => file + } } private def makeClassLoader(): util.AbstractFileClassLoader = new TranslatingClassLoader(parentClassLoader match { -- cgit v1.2.3