Skip to content

Commit abbbcef

Browse files
authored
Merge pull request #11496 from som-snytt/issue/11495
AbstractFileClassLoader does resources
2 parents 461b561 + 6c92bbf commit abbbcef

File tree

3 files changed

+181
-14
lines changed

3 files changed

+181
-14
lines changed

compiler/src/dotty/tools/io/AbstractFile.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ abstract class AbstractFile extends Iterable[AbstractFile] {
195195
/** Returns all abstract subfiles of this abstract directory. */
196196
def iterator(): Iterator[AbstractFile]
197197

198+
/** Drill down through subdirs looking for the target, as in lookupName.
199+
* Ths target name is the last of parts.
200+
*/
201+
final def lookupPath(parts: Seq[String], directory: Boolean): AbstractFile =
202+
var file: AbstractFile = this
203+
var i = 0
204+
val n = parts.length - 1
205+
while file != null && i < n do
206+
file = file.lookupName(parts(i), directory = true)
207+
i += 1
208+
if file == null then null else file.lookupName(parts(i), directory = directory)
209+
end lookupPath
210+
198211
/** Returns the abstract file in this abstract directory with the specified
199212
* name. If there is no such file, returns `null`. The argument
200213
* `directory` tells whether to look for a directory or
Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
113
package dotty.tools
214
package repl
315

416
import io.AbstractFile
517

6-
/**
7-
* A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}.
8-
*
9-
* @author Lex Spoon
10-
*/
11-
class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader)
12-
extends ClassLoader(parent)
13-
{
18+
import java.net.{URL, URLConnection, URLStreamHandler}
19+
import java.util.Collections
20+
21+
class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) extends ClassLoader(parent):
22+
private def findAbstractFile(name: String) = root.lookupPath(name.split('/').toIndexedSeq, directory = false)
23+
24+
override protected def findResource(name: String) =
25+
findAbstractFile(name) match
26+
case null => null
27+
case file => new URL(null, s"memory:${file.path}", new URLStreamHandler {
28+
override def openConnection(url: URL): URLConnection = new URLConnection(url) {
29+
override def connect() = ()
30+
override def getInputStream = file.input
31+
}
32+
})
33+
override protected def findResources(name: String) =
34+
findResource(name) match
35+
case null => Collections.enumeration(Collections.emptyList[URL]) //Collections.emptyEnumeration[URL]
36+
case url => Collections.enumeration(Collections.singleton(url))
37+
1438
override def findClass(name: String): Class[?] = {
1539
var file: AbstractFile = root
1640
val pathParts = name.split("[./]").toList
@@ -28,9 +52,5 @@ extends ClassLoader(parent)
2852
defineClass(name, bytes, 0, bytes.length)
2953
}
3054

31-
override def loadClass(name: String): Class[?] =
32-
try findClass(name)
33-
catch {
34-
case _: ClassNotFoundException => super.loadClass(name)
35-
}
36-
}
55+
override def loadClass(name: String): Class[?] = try findClass(name) catch case _: ClassNotFoundException => super.loadClass(name)
56+
end AbstractFileClassLoader
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package dotty.tools.repl
2+
3+
import org.junit.Assert.*
4+
import org.junit.Test
5+
6+
class AbstractFileClassLoaderTest:
7+
8+
import dotty.tools.io.{AbstractFile, VirtualDirectory}
9+
import scala.collection.mutable.ArrayBuffer
10+
import scala.io.{Codec, Source}, Codec.UTF8
11+
import java.io.{BufferedInputStream, Closeable, InputStream}
12+
import java.net.{URLClassLoader, URL}
13+
14+
given `we love utf8`: Codec = UTF8
15+
16+
def closing[T <: Closeable, U](stream: T)(f: T => U): U = try f(stream) finally stream.close()
17+
18+
extension (f: AbstractFile) def writeContent(s: String): Unit = closing(f.bufferedOutput)(_.write(s.getBytes(UTF8.charSet)))
19+
def slurp(inputStream: => InputStream)(implicit codec: Codec): String = closing(Source.fromInputStream(inputStream)(codec))(_.mkString)
20+
def slurp(url: URL)(implicit codec: Codec): String = slurp(url.openStream())
21+
22+
extension (input: InputStream) def bytes: Array[Byte] =
23+
val bis = new BufferedInputStream(input)
24+
val it = Iterator.continually(bis.read()).takeWhile(_ != -1).map(_.toByte)
25+
new ArrayBuffer[Byte]().addAll(it).toArray
26+
27+
// cf ScalaClassLoader#classBytes
28+
extension (loader: ClassLoader)
29+
// An InputStream representing the given class name, or null if not found.
30+
def classAsStream(className: String) = loader.getResourceAsStream {
31+
if className.endsWith(".class") then className
32+
else s"${className.replace('.', '/')}.class" // classNameToPath
33+
}
34+
// The actual bytes for a class file, or an empty array if it can't be found.
35+
def classBytes(className: String): Array[Byte] = classAsStream(className) match
36+
case null => Array()
37+
case stream => stream.bytes
38+
39+
val NoClassLoader: ClassLoader = null
40+
41+
// virtual dir "fuzz" and "fuzz/buzz/booz.class"
42+
def fuzzBuzzBooz: (AbstractFile, AbstractFile) =
43+
val fuzz = new VirtualDirectory("fuzz", None)
44+
val buzz = fuzz.subdirectoryNamed("buzz")
45+
val booz = buzz.fileNamed("booz.class")
46+
(fuzz, booz)
47+
48+
@Test def afclGetsParent(): Unit =
49+
val p = new URLClassLoader(Array.empty[URL])
50+
val d = new VirtualDirectory("vd", None)
51+
val x = new AbstractFileClassLoader(d, p)
52+
assertSame(p, x.getParent)
53+
54+
@Test def afclGetsResource(): Unit =
55+
val (fuzz, booz) = fuzzBuzzBooz
56+
booz.writeContent("hello, world")
57+
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
58+
val res = sut.getResource("buzz/booz.class")
59+
assertNotNull("Find buzz/booz.class", res)
60+
assertEquals("hello, world", slurp(res))
61+
62+
@Test def afclGetsResourceFromParent(): Unit =
63+
val (fuzz, booz) = fuzzBuzzBooz
64+
val (fuzz_, booz_) = fuzzBuzzBooz
65+
booz.writeContent("hello, world")
66+
booz_.writeContent("hello, world_")
67+
val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
68+
val sut = new AbstractFileClassLoader(fuzz_, p)
69+
val res = sut.getResource("buzz/booz.class")
70+
assertNotNull("Find buzz/booz.class", res)
71+
assertEquals("hello, world", slurp(res))
72+
73+
@Test def afclGetsResourceInDefaultPackage(): Unit =
74+
val fuzz = new VirtualDirectory("fuzz", None)
75+
val booz = fuzz.fileNamed("booz.class")
76+
val bass = fuzz.fileNamed("bass")
77+
booz.writeContent("hello, world")
78+
bass.writeContent("lo tone")
79+
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
80+
val res = sut.getResource("booz.class")
81+
assertNotNull(res)
82+
assertEquals("hello, world", slurp(res))
83+
assertEquals("lo tone", slurp(sut.getResource("bass")))
84+
85+
// scala/bug#8843
86+
@Test def afclGetsResources(): Unit =
87+
val (fuzz, booz) = fuzzBuzzBooz
88+
booz.writeContent("hello, world")
89+
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
90+
val e = sut.getResources("buzz/booz.class")
91+
assertTrue("At least one buzz/booz.class", e.hasMoreElements)
92+
assertEquals("hello, world", slurp(e.nextElement))
93+
assertFalse(e.hasMoreElements)
94+
95+
@Test def afclGetsResourcesFromParent(): Unit =
96+
val (fuzz, booz) = fuzzBuzzBooz
97+
val (fuzz_, booz_) = fuzzBuzzBooz
98+
booz.writeContent("hello, world")
99+
booz_.writeContent("hello, world_")
100+
val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
101+
val x = new AbstractFileClassLoader(fuzz_, p)
102+
val e = x.getResources("buzz/booz.class")
103+
assertTrue(e.hasMoreElements)
104+
assertEquals("hello, world", slurp(e.nextElement))
105+
assertTrue(e.hasMoreElements)
106+
assertEquals("hello, world_", slurp(e.nextElement))
107+
assertFalse(e.hasMoreElements)
108+
109+
@Test def afclGetsResourceAsStream(): Unit =
110+
val (fuzz, booz) = fuzzBuzzBooz
111+
booz.writeContent("hello, world")
112+
val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
113+
val r = x.getResourceAsStream("buzz/booz.class")
114+
assertNotNull(r)
115+
assertEquals("hello, world", closing(r)(is => Source.fromInputStream(is).mkString))
116+
117+
@Test def afclGetsClassBytes(): Unit =
118+
val (fuzz, booz) = fuzzBuzzBooz
119+
booz.writeContent("hello, world")
120+
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
121+
val b = sut.classBytes("buzz/booz.class")
122+
assertEquals("hello, world", new String(b, UTF8.charSet))
123+
124+
@Test def afclGetsClassBytesFromParent(): Unit =
125+
val (fuzz, booz) = fuzzBuzzBooz
126+
val (fuzz_, booz_) = fuzzBuzzBooz
127+
booz.writeContent("hello, world")
128+
booz_.writeContent("hello, world_")
129+
130+
val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
131+
val sut = new AbstractFileClassLoader(fuzz_, p)
132+
val b = sut.classBytes("buzz/booz.class")
133+
assertEquals("hello, world", new String(b, UTF8.charSet))
134+
end AbstractFileClassLoaderTest

0 commit comments

Comments
 (0)