Skip to content

Commit 82f9543

Browse files
committed
AbstractFileClassLoader does resources
1 parent aa7c21e commit 82f9543

File tree

3 files changed

+180
-14
lines changed

3 files changed

+180
-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: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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.io.Source
10+
import scala.io.Codec, Codec.UTF8
11+
import java.io.{Closeable, InputStream}
12+
import java.net.{URLClassLoader, URL}
13+
14+
implicit def `we love utf8`: Codec = UTF8
15+
16+
/** Call a function on something Closeable, finally closing it. */
17+
def closing[T <: Closeable, U](stream: T)(f: T => U): U = try f(stream) finally stream.close()
18+
19+
extension (f: AbstractFile) def writeContent(s: String): Unit = closing(f.bufferedOutput)(_.write(s.getBytes(UTF8.charSet)))
20+
def slurp(inputStream: => InputStream)(implicit codec: Codec): String =
21+
val src = Source.fromInputStream(inputStream)(codec)
22+
try src.mkString finally src.close() // Always Be Closing
23+
def slurp(url: URL)(implicit codec: Codec): String = slurp(url.openStream())
24+
25+
val NoClassLoader: ClassLoader = null
26+
27+
// virtual dir "fuzz" and "fuzz/buzz/booz.class"
28+
def fuzzBuzzBooz: (AbstractFile, AbstractFile) =
29+
val fuzz = new VirtualDirectory("fuzz", None)
30+
val buzz = fuzz.subdirectoryNamed("buzz")
31+
val booz = buzz.fileNamed("booz.class")
32+
(fuzz, booz)
33+
34+
@Test
35+
def afclGetsParent(): Unit =
36+
val p = new URLClassLoader(Array.empty[URL])
37+
val d = new VirtualDirectory("vd", None)
38+
val x = new AbstractFileClassLoader(d, p)
39+
assertSame(p, x.getParent)
40+
41+
@Test
42+
def afclGetsResource(): Unit =
43+
val (fuzz, booz) = fuzzBuzzBooz
44+
booz.writeContent("hello, world")
45+
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
46+
val res = sut.getResource("buzz/booz.class")
47+
assertNotNull("Find buzz/booz.class", res)
48+
assertEquals("hello, world", slurp(res))
49+
50+
@Test
51+
def afclGetsResourceFromParent(): Unit =
52+
val (fuzz, booz) = fuzzBuzzBooz
53+
val (fuzz_, booz_) = fuzzBuzzBooz
54+
booz.writeContent("hello, world")
55+
booz_.writeContent("hello, world_")
56+
val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
57+
val sut = new AbstractFileClassLoader(fuzz_, p)
58+
val res = sut.getResource("buzz/booz.class")
59+
assertNotNull("Find buzz/booz.class", res)
60+
assertEquals("hello, world", slurp(res))
61+
62+
@Test
63+
def afclGetsResourceInDefaultPackage(): Unit =
64+
val fuzz = new VirtualDirectory("fuzz", None)
65+
val booz = fuzz.fileNamed("booz.class")
66+
val bass = fuzz.fileNamed("bass")
67+
booz.writeContent("hello, world")
68+
bass.writeContent("lo tone")
69+
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
70+
val res = sut.getResource("booz.class")
71+
assertNotNull(res)
72+
assertEquals("hello, world", slurp(res))
73+
assertEquals("lo tone", slurp(sut.getResource("bass")))
74+
75+
// scala/bug#8843
76+
@Test
77+
def afclGetsResources(): Unit =
78+
val (fuzz, booz) = fuzzBuzzBooz
79+
booz.writeContent("hello, world")
80+
val sut = new AbstractFileClassLoader(fuzz, NoClassLoader)
81+
val e = sut.getResources("buzz/booz.class")
82+
assertTrue("At least one buzz/booz.class", e.hasMoreElements)
83+
assertEquals("hello, world", slurp(e.nextElement))
84+
assertFalse(e.hasMoreElements)
85+
86+
@Test
87+
def afclGetsResourcesFromParent(): Unit =
88+
val (fuzz, booz) = fuzzBuzzBooz
89+
val (fuzz_, booz_) = fuzzBuzzBooz
90+
booz.writeContent("hello, world")
91+
booz_.writeContent("hello, world_")
92+
val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
93+
val x = new AbstractFileClassLoader(fuzz_, p)
94+
val e = x.getResources("buzz/booz.class")
95+
assertTrue(e.hasMoreElements)
96+
assertEquals("hello, world", slurp(e.nextElement))
97+
assertTrue(e.hasMoreElements)
98+
assertEquals("hello, world_", slurp(e.nextElement))
99+
assertFalse(e.hasMoreElements)
100+
101+
@Test
102+
def afclGetsResourceAsStream(): Unit =
103+
val (fuzz, booz) = fuzzBuzzBooz
104+
booz.writeContent("hello, world")
105+
val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
106+
val r = x.getResourceAsStream("buzz/booz.class")
107+
assertNotNull(r)
108+
assertEquals("hello, world", closing(r)(is => Source.fromInputStream(is).mkString))
109+
110+
/*
111+
@Test
112+
def afclGetsClassBytes(): Unit = {
113+
val (fuzz, booz) = fuzzBuzzBooz
114+
booz writeContent "hello, world"
115+
val x = new AbstractFileClassLoader(fuzz, NoClassLoader)
116+
val b = x.classBytes("buzz/booz.class")
117+
assertEquals("hello, world", new String(b, UTF8.charSet))
118+
}
119+
120+
@Test
121+
def afclGetsClassBytesFromParent(): Unit = {
122+
val (fuzz, booz) = fuzzBuzzBooz
123+
val (fuzz_, booz_) = fuzzBuzzBooz
124+
booz writeContent "hello, world"
125+
booz_ writeContent "hello, world_"
126+
127+
val p = new AbstractFileClassLoader(fuzz, NoClassLoader)
128+
val x = new AbstractFileClassLoader(fuzz_, p)
129+
val b = x.classBytes("buzz/booz.class")
130+
assertEquals("hello, world", new String(b, UTF8.charSet))
131+
}
132+
*/
133+
end AbstractFileClassLoaderTest

0 commit comments

Comments
 (0)