Skip to content

AbstractFileClassLoader does resources #11496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions compiler/src/dotty/tools/io/AbstractFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,19 @@ abstract class AbstractFile extends Iterable[AbstractFile] {
/** Returns all abstract subfiles of this abstract directory. */
def iterator(): Iterator[AbstractFile]

/** Drill down through subdirs looking for the target, as in lookupName.
* Ths target name is the last of parts.
*/
final def lookupPath(parts: Seq[String], directory: Boolean): AbstractFile =
var file: AbstractFile = this
var i = 0
val n = parts.length - 1
while file != null && i < n do
file = file.lookupName(parts(i), directory = true)
i += 1
if file == null then null else file.lookupName(parts(i), directory = directory)
end lookupPath

/** Returns the abstract file in this abstract directory with the specified
* name. If there is no such file, returns `null`. The argument
* `directory` tells whether to look for a directory or
Expand Down
48 changes: 34 additions & 14 deletions compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package dotty.tools
package repl

import io.AbstractFile

/**
* A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}.
*
* @author Lex Spoon
*/
class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader)
extends ClassLoader(parent)
{
import java.net.{URL, URLConnection, URLStreamHandler}
import java.util.Collections

class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) extends ClassLoader(parent):
private def findAbstractFile(name: String) = root.lookupPath(name.split('/').toIndexedSeq, directory = false)

override protected def findResource(name: String) =
findAbstractFile(name) match
case null => null
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 getInputStream = file.input
}
})
override protected def findResources(name: String) =
findResource(name) match
case null => Collections.enumeration(Collections.emptyList[URL]) //Collections.emptyEnumeration[URL]
case url => Collections.enumeration(Collections.singleton(url))

override def findClass(name: String): Class[?] = {
var file: AbstractFile = root
val pathParts = name.split("[./]").toList
Expand All @@ -28,9 +52,5 @@ extends ClassLoader(parent)
defineClass(name, bytes, 0, bytes.length)
}

override def loadClass(name: String): Class[?] =
try findClass(name)
catch {
case _: ClassNotFoundException => super.loadClass(name)
}
}
override def loadClass(name: String): Class[?] = try findClass(name) catch case _: ClassNotFoundException => super.loadClass(name)
end AbstractFileClassLoader
133 changes: 133 additions & 0 deletions compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package dotty.tools.repl

import org.junit.Assert._
import org.junit.Test

class AbstractFileClassLoaderTest:

import dotty.tools.io.{AbstractFile, VirtualDirectory}
import scala.io.Source
import scala.io.Codec, Codec.UTF8
import java.io.{Closeable, InputStream}
import java.net.{URLClassLoader, URL}

implicit def `we love utf8`: Codec = UTF8

/** Call a function on something Closeable, finally closing it. */
def closing[T <: Closeable, U](stream: T)(f: T => U): U = try f(stream) finally stream.close()

extension (f: AbstractFile) def writeContent(s: String): Unit = closing(f.bufferedOutput)(_.write(s.getBytes(UTF8.charSet)))
def slurp(inputStream: => InputStream)(implicit codec: Codec): String =
val src = Source.fromInputStream(inputStream)(codec)
try src.mkString finally src.close() // Always Be Closing
def slurp(url: URL)(implicit codec: Codec): String = slurp(url.openStream())

val NoClassLoader: ClassLoader = null

// virtual dir "fuzz" and "fuzz/buzz/booz.class"
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 sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
val res = sut.getResource("buzz/booz.class")
assertNotNull("Find buzz/booz.class", res)
assertEquals("hello, world", slurp(res))

@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 sut = new AbstractFileClassLoader(fuzz_, p)
val res = sut.getResource("buzz/booz.class")
assertNotNull("Find buzz/booz.class", res)
assertEquals("hello, world", slurp(res))

@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 sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
val res = sut.getResource("booz.class")
assertNotNull(res)
assertEquals("hello, world", slurp(res))
assertEquals("lo tone", slurp(sut.getResource("bass")))

// scala/bug#8843
@Test
def afclGetsResources(): Unit =
val (fuzz, booz) = fuzzBuzzBooz
booz.writeContent("hello, world")
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
val e = sut.getResources("buzz/booz.class")
assertTrue("At least one buzz/booz.class", e.hasMoreElements)
assertEquals("hello, world", slurp(e.nextElement))
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", slurp(e.nextElement))
assertTrue(e.hasMoreElements)
assertEquals("hello, world_", slurp(e.nextElement))
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", closing(r)(is => Source.fromInputStream(is).mkString))

/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you leave a TODO explaining what's missing to get these tests to pass?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API doesn't apply, but I added something testable.

@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))
}
*/
end AbstractFileClassLoaderTest