ammonite.runtime.ClassLoaders.scala Maven / Gradle / Ivy
package ammonite.runtime
import java.io.ByteArrayInputStream
import java.net.{URL, URLClassLoader, URLConnection, URLStreamHandler}
import java.nio.ByteBuffer
import java.nio.file.Files
import java.util.{Collections, Enumeration}
import acyclic.file
import ammonite.ops._
import ammonite.util.{Imports, Util}
import scala.collection.mutable
/**
* Represents a single "frame" of the `sess.save`/`sess.load` stack/tree.
*
* Exposes `imports` and `classpath` as readable but only writable
* in particular ways: `imports` can only be updated via `mergeImports`,
* while `classpath` can only be added to.
*/
class Frame(val classloader: SpecialClassLoader,
val pluginClassloader: SpecialClassLoader,
private[this] var imports0: Imports,
private[this] var classpath0: Seq[java.io.File]){
def imports = imports0
def classpath = classpath0
def addImports(additional: Imports) = {
imports0 = imports0 ++ additional
}
def addClasspath(additional: Seq[java.io.File]) = {
additional.map(_.toURI.toURL).foreach(classloader.add)
classpath0 = classpath0 ++ additional
}
}
object SpecialClassLoader{
val simpleNameRegex = "[a-zA-Z0-9_]+".r
/**
* Stats all loose class-files in the current classpath that could
* conceivably be part of some package, i.e. their directory path
* doesn't contain any non-package-identifier segments, and aggregates
* their names and mtimes as a "signature" of the current classpath
*/
def initialClasspathSignature(classloader: ClassLoader): Seq[(Path, Long)] = {
val allClassloaders = {
val all = mutable.Buffer.empty[ClassLoader]
var current = classloader
while(current != null){
all.append(current)
current = current.getParent
}
all
}
def findMtimes(d: java.nio.file.Path): Seq[(Path, Long)] = {
def skipSuspicious(path: Path) = {
simpleNameRegex.findPrefixOf(path.last) == Some(path.last)
}
ls.rec(skip = skipSuspicious)! Path(d) | (x => (x, x.mtime.toMillis))
}
val classpathFolders =
allClassloaders.collect{case cl: java.net.URLClassLoader => cl.getURLs}
.flatten
.filter(_.getProtocol == "file")
.map(_.toURI)
.map(java.nio.file.Paths.get)
.filter(java.nio.file.Files.isDirectory(_))
val classFileMtimes = classpathFolders.flatMap(f => findMtimes(f))
classFileMtimes
}
}
/**
* Classloader used to implement the jar-downloading
* command-evaluating logic in Ammonite.
*
* http://stackoverflow.com/questions/3544614/how-is-the-control-flow-to-findclass-of
*/
class SpecialClassLoader(specialLocalClasses: Set[String], parent: ClassLoader, parentSignature: Seq[(Path, Long)])
extends URLClassLoader(Array(), parent){
def cloneClassLoader(): SpecialClassLoader = {
val clone = parent match {
case scl: SpecialClassLoader =>
val p = scl.cloneClassLoader()
new SpecialClassLoader(specialLocalClasses, p, p.classpathSignature)
case other =>
new SpecialClassLoader(specialLocalClasses, other, SpecialClassLoader.initialClasspathSignature(other))
}
for (url <- getURLs)
clone.add(url)
for ((name, bytes) <- newFileDict)
clone.addClassFile(name, bytes)
clone
}
/**
* Files which have been compiled, stored so that our special
* classloader can get at them.
*/
val newFileDict = mutable.Map.empty[String, Array[Byte]]
def addClassFile(name: String, bytes: Array[Byte]) = {
val tuple = Path(name, root) -> bytes.sum.hashCode().toLong
classpathSignature0 = classpathSignature0 ++ Seq(tuple)
newFileDict(name) = bytes
}
def findClassPublic(name: String) = findClass(name)
override def findClass(name: String): Class[_] = {
val loadedClass = this.findLoadedClass(name)
if (loadedClass != null) loadedClass
else if (newFileDict.contains(name)) {
val bytes = newFileDict(name)
defineClass(name, bytes, 0, bytes.length)
}else if (specialLocalClasses(name)) {
import ammonite.ops._
val resource = this.getResourceAsStream(name.replace('.', '/') + ".class")
if (resource != null){
if (sys.env.contains("DEBUG") || sys.props.contains("DEBUG")) println(s"Found resource for $name")
val bytes = read.bytes(resource)
defineClass(name, bytes, 0, bytes.length)
}else{
if (sys.env.contains("DEBUG") || sys.props.contains("DEBUG")) println(s"Resource for $name not found")
super.findClass(name)
}
} else super.findClass(name)
}
def add(url: URL) = {
classpathSignature0 = classpathSignature0 ++ Seq(jarSignature(url))
this.addURL(url)
}
override def close() = {
// DO NOTHING LOLZ
// Works around
// https://github.com/scala/scala/commit/6181525f60588228ce99ab3ef2593ecfcfd35066
// Which for some reason started mysteriously closing these classloaders in 2.12
}
private def jarSignature(url: URL) = {
val path = Path(java.nio.file.Paths.get(url.toURI()).toFile(), root)
path -> path.mtime.toMillis
}
private[this] var classpathSignature0 = parentSignature
def classpathSignature = classpathSignature0
def classpathHash = {
Util.md5Hash(
classpathSignature0.iterator.map { case (path, long) =>
val buffer = ByteBuffer.allocate(8)
buffer.putLong(long)
path.toString.getBytes ++ buffer.array()
}
)
}
def allJars: Seq[URL] = {
this.getURLs ++ ( parent match{
case t: SpecialClassLoader => t.allJars
case _ => Nil
})
}
override def findResource(name: String) =
getURLFromFileDict(name).getOrElse(super.findResource(name))
override def findResources(name: String) = getURLFromFileDict(name) match {
case Some(u) => Collections.enumeration(Collections.singleton(u))
case None => super.findResources(name)
}
private def getURLFromFileDict(name: String) = {
val className = name.stripSuffix(".class").replace('/', '.')
newFileDict.get(className) map { x =>
new URL(null, s"memory:${name}", new URLStreamHandler {
override def openConnection(url: URL): URLConnection = new URLConnection(url) {
override def connect() = ()
override def getInputStream = new ByteArrayInputStream(x)
}
})
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy