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 all commits
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
134 changes: 134 additions & 0 deletions compiler/test/dotty/tools/repl/AbstractFileClassLoaderTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package dotty.tools.repl

import org.junit.Assert.*
import org.junit.Test

class AbstractFileClassLoaderTest:

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

given `we love utf8`: Codec = UTF8

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 = closing(Source.fromInputStream(inputStream)(codec))(_.mkString)
def slurp(url: URL)(implicit codec: Codec): String = slurp(url.openStream())

extension (input: InputStream) def bytes: Array[Byte] =
val bis = new BufferedInputStream(input)
val it = Iterator.continually(bis.read()).takeWhile(_ != -1).map(_.toByte)
new ArrayBuffer[Byte]().addAll(it).toArray

// cf ScalaClassLoader#classBytes
extension (loader: ClassLoader)
// An InputStream representing the given class name, or null if not found.
def classAsStream(className: String) = loader.getResourceAsStream {
if className.endsWith(".class") then className
else s"${className.replace('.', '/')}.class" // classNameToPath
}
// The actual bytes for a class file, or an empty array if it can't be found.
def classBytes(className: String): Array[Byte] = classAsStream(className) match
case null => Array()
case stream => stream.bytes

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))

@Test def afclGetsClassBytes(): Unit =
val (fuzz, booz) = fuzzBuzzBooz
booz.writeContent("hello, world")
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
val b = sut.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 sut = new AbstractFileClassLoader(fuzz_, p)
val b = sut.classBytes("buzz/booz.class")
assertEquals("hello, world", new String(b, UTF8.charSet))
end AbstractFileClassLoaderTest