summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Kossakowski <grzegorz.kossakowski@gmail.com>2014-10-07 16:33:50 +0200
committerGrzegorz Kossakowski <grzegorz.kossakowski@gmail.com>2014-10-07 16:33:50 +0200
commit0940f19dc6809ee7622dda1b76121af628d5b435 (patch)
treeb00af3954433cbaa837285501a309e1639122cac
parentcd46dc31140b73eec08b04916310d316aa2df3ea (diff)
parent964a197cd90e561d05c9d725cc13895f18b6a6d0 (diff)
downloadscala-0940f19dc6809ee7622dda1b76121af628d5b435.tar.gz
scala-0940f19dc6809ee7622dda1b76121af628d5b435.tar.bz2
scala-0940f19dc6809ee7622dda1b76121af628d5b435.zip
Merge pull request #4030 from som-snytt/issue/8843
SI-8843 AbsFileCL acts like a CL
-rw-r--r--src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala66
-rw-r--r--src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala6
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala34
-rw-r--r--test/files/run/t8843-repl-xlat.scala33
-rw-r--r--test/junit/scala/reflect/internal/PrintersTest.scala6
-rw-r--r--test/junit/scala/reflect/internal/util/AbstractFileClassLoaderTest.scala138
6 files changed, 230 insertions, 53 deletions
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 {
diff --git a/test/files/run/t8843-repl-xlat.scala b/test/files/run/t8843-repl-xlat.scala
new file mode 100644
index 0000000000..6426dbe7d4
--- /dev/null
+++ b/test/files/run/t8843-repl-xlat.scala
@@ -0,0 +1,33 @@
+
+import scala.tools.partest.SessionTest
+
+// Handy hamburger helper for repl resources
+object Test extends SessionTest {
+ def session =
+"""Type in expressions to have them evaluated.
+Type :help for more information.
+
+scala> $intp.isettings.unwrapStrings = false
+$intp.isettings.unwrapStrings: Boolean = false
+
+scala> class Bippy
+defined class Bippy
+
+scala> $intp.classLoader getResource "Bippy.class"
+res0: java.net.URL = memory:(memory)/$line4/$read$$iw$$iw$Bippy.class
+
+scala> ($intp.classLoader getResources "Bippy.class").nextElement
+res1: java.net.URL = memory:(memory)/$line4/$read$$iw$$iw$Bippy.class
+
+scala> ($intp.classLoader classBytes "Bippy").nonEmpty
+res2: Boolean = true
+
+scala> ($intp.classLoader classAsStream "Bippy") != null
+res3: Boolean = true
+
+scala> $intp.classLoader getResource "Bippy"
+res4: java.net.URL = null
+
+scala> :quit"""
+}
+
diff --git a/test/junit/scala/reflect/internal/PrintersTest.scala b/test/junit/scala/reflect/internal/PrintersTest.scala
index 1458b942dc..ca9b4671b2 100644
--- a/test/junit/scala/reflect/internal/PrintersTest.scala
+++ b/test/junit/scala/reflect/internal/PrintersTest.scala
@@ -24,10 +24,10 @@ object PrinterHelper {
resultCode.lines mkString s"$LF"
def assertResultCode(code: String)(parsedCode: String = "", typedCode: String = "", wrap: Boolean = false, printRoot: Boolean = false) = {
- def toolboxTree(tree: => Tree) = try{
+ def toolboxTree(tree: => Tree) = try {
tree
} catch {
- case e:scala.tools.reflect.ToolBoxError => throw new Exception(e.getMessage + ": " + code)
+ case e:scala.tools.reflect.ToolBoxError => throw new Exception(e.getMessage + ": " + code, e)
}
def wrapCode(source: String) = {
@@ -1186,4 +1186,4 @@ trait QuasiTreesPrintTests {
| };
| ()
|}""")
-} \ No newline at end of file
+}
diff --git a/test/junit/scala/reflect/internal/util/AbstractFileClassLoaderTest.scala b/test/junit/scala/reflect/internal/util/AbstractFileClassLoaderTest.scala
new file mode 100644
index 0000000000..a2537ddab7
--- /dev/null
+++ b/test/junit/scala/reflect/internal/util/AbstractFileClassLoaderTest.scala
@@ -0,0 +1,138 @@
+package scala.reflect.internal.util
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(classOf[JUnit4])
+class AbstractFileClassLoaderTest {
+
+ import scala.reflect.io._
+ import scala.io.Source
+ import scala.io.Codec.UTF8
+ import scala.reflect.io.Streamable
+ import java.net.{ URLClassLoader, URL }
+
+ implicit def `we love utf8` = UTF8
+ implicit class `abs file ops`(f: AbstractFile) {
+ def writeContent(s: String): Unit = Streamable.closing(f.bufferedOutput)(os => os write s.getBytes(UTF8.charSet))
+ }
+ implicit class `url slurp`(url: URL) {
+ def slurp(): String = Streamable.slurp(url)
+ }
+
+ val NoClassLoader: ClassLoader = null
+
+ def fuzzBuzzBooz: (AbstractFile, AbstractFile) = {
+ val fuzz = new VirtualDirectory("fuzz", None)
+ val buzz = fuzz subdirectoryNamed "buzz"
+ val booz = buzz fileNamed "booz.class"
+ (fuzz, booz)
+ }
+
+ @Test
+ def afclGetsParent(): Unit = {
+ val p = new URLClassLoader(Array.empty[URL])
+ val d = new VirtualDirectory("vd", None)
+ val x = new AbstractFileClassLoader(d, p)
+ assertSame(p, x.getParent)
+ }
+
+ @Test
+ def afclGetsResource(): Unit = {
+ val (fuzz, booz) = fuzzBuzzBooz
+ booz writeContent "hello, world"
+ val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val r = x.getResource("buzz/booz.class")
+ assertNotNull(r)
+ assertEquals("hello, world", r.slurp())
+ }
+
+ @Test
+ def afclGetsResourceFromParent(): Unit = {
+ val (fuzz, booz) = fuzzBuzzBooz
+ val (fuzz_, booz_) = fuzzBuzzBooz
+ booz writeContent "hello, world"
+ booz_ writeContent "hello, world_"
+ val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val x = new AbstractFileClassLoader(fuzz_, p)
+ val r = x.getResource("buzz/booz.class")
+ assertNotNull(r)
+ assertEquals("hello, world", r.slurp())
+ }
+
+ @Test
+ def afclGetsResourceInDefaultPackage(): Unit = {
+ val fuzz = new VirtualDirectory("fuzz", None)
+ val booz = fuzz fileNamed "booz.class"
+ val bass = fuzz fileNamed "bass"
+ booz writeContent "hello, world"
+ bass writeContent "lo tone"
+ val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val r = x.getResource("booz.class")
+ assertNotNull(r)
+ assertEquals("hello, world", r.slurp())
+ assertEquals("lo tone", (x getResource "bass").slurp())
+ }
+
+ // SI-8843
+ @Test
+ def afclGetsResources(): Unit = {
+ val (fuzz, booz) = fuzzBuzzBooz
+ booz writeContent "hello, world"
+ val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val e = x.getResources("buzz/booz.class")
+ assertTrue(e.hasMoreElements)
+ assertEquals("hello, world", e.nextElement.slurp())
+ assertFalse(e.hasMoreElements)
+ }
+
+ @Test
+ def afclGetsResourcesFromParent(): Unit = {
+ val (fuzz, booz) = fuzzBuzzBooz
+ val (fuzz_, booz_) = fuzzBuzzBooz
+ booz writeContent "hello, world"
+ booz_ writeContent "hello, world_"
+ val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val x = new AbstractFileClassLoader(fuzz_, p)
+ val e = x.getResources("buzz/booz.class")
+ assertTrue(e.hasMoreElements)
+ assertEquals("hello, world", e.nextElement.slurp())
+ assertTrue(e.hasMoreElements)
+ assertEquals("hello, world_", e.nextElement.slurp())
+ assertFalse(e.hasMoreElements)
+ }
+
+ @Test
+ def afclGetsResourceAsStream(): Unit = {
+ val (fuzz, booz) = fuzzBuzzBooz
+ booz writeContent "hello, world"
+ val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val r = x.getResourceAsStream("buzz/booz.class")
+ assertNotNull(r)
+ assertEquals("hello, world", Streamable.closing(r)(is => Source.fromInputStream(is).mkString))
+ }
+
+ @Test
+ def afclGetsClassBytes(): Unit = {
+ val (fuzz, booz) = fuzzBuzzBooz
+ booz writeContent "hello, world"
+ val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val b = x.classBytes("buzz/booz.class")
+ assertEquals("hello, world", new String(b, UTF8.charSet))
+ }
+
+ @Test
+ def afclGetsClassBytesFromParent(): Unit = {
+ val (fuzz, booz) = fuzzBuzzBooz
+ val (fuzz_, booz_) = fuzzBuzzBooz
+ booz writeContent "hello, world"
+ booz_ writeContent "hello, world_"
+
+ val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
+ val x = new AbstractFileClassLoader(fuzz_, p)
+ val b = x.classBytes("buzz/booz.class")
+ assertEquals("hello, world", new String(b, UTF8.charSet))
+ }
+}