From 8f6b4743a0fac93f671e9eff3fca2b4dec4bf935 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Mon, 20 May 2013 11:29:10 +0200 Subject: Moves AbstractFileClassLoader to scala-reflect.jar Its string name was used in ReflectionUtils and became broken after repl got factored out. This hints that that classloader belongs to where it is used, i.e. to scala-reflect.jar. Moreover AbstractFile is defined in scala-reflect.jar, so it's only logical to also define a derived classloader in scala-reflect.jar. --- .../internal/util/AbstractFileClassLoader.scala | 122 ++++++++++++++++++++ .../reflect/internal/util/ScalaClassLoader.scala | 124 +++++++++++++++++++++ .../scala/reflect/runtime/ReflectionUtils.scala | 3 +- 3 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala create mode 100644 src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala (limited to 'src/reflect') diff --git a/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala new file mode 100644 index 0000000000..10a8b4c812 --- /dev/null +++ b/src/reflect/scala/reflect/internal/util/AbstractFileClassLoader.scala @@ -0,0 +1,122 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + */ + +package scala +package reflect.internal.util + +import scala.reflect.io.AbstractFile +import java.security.cert.Certificate +import java.security.{ ProtectionDomain, CodeSource } +import java.net.{ URL, URLConnection, URLStreamHandler } +import scala.collection.{ mutable, immutable } + +/** + * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. + * + * @author Lex Spoon + */ +class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) + extends ClassLoader(parent) + with ScalaClassLoader +{ + protected def classNameToPath(name: String): String = + if (name endsWith ".class") name + else name.replace('.', '/') + ".class" + + protected def findAbstractFile(name: String): AbstractFile = { + var file: AbstractFile = root + val pathParts = name split '/' + + for (dirPart <- pathParts.init) { + file = file.lookupName(dirPart, directory = true) + if (file == null) + return null + } + + file.lookupName(pathParts.last, directory = false) match { + case null => null + case file => file + } + } + + protected def dirNameToPath(name: String): String = + name.replace('.', '/') + + protected def findAbstractDir(name: String): AbstractFile = { + var file: AbstractFile = root + val pathParts = dirNameToPath(name) split '/' + + for (dirPart <- pathParts) { + file = file.lookupName(dirPart, directory = true) + if (file == null) + return null + } + + file + } + + // parent delegation in JCL uses getResource; so either add parent.getResAsStream + // or implement findResource, which we do here as a study in scarlet (my complexion + // after looking at CLs and URLs) + override def findResource(name: String): URL = findAbstractFile(name) match { + case null => null + case file => new URL(null, "repldir:" + file.path, new URLStreamHandler { + override def openConnection(url: URL): URLConnection = new URLConnection(url) { + override def connect() { } + override def getInputStream = file.input + } + }) + } + + // this inverts delegation order: super.getResAsStr calls parent.getRes if we fail + override def getResourceAsStream(name: String) = findAbstractFile(name) match { + case null => super.getResourceAsStream(name) + case file => file.input + } + // ScalaClassLoader.classBytes uses getResAsStream, so we'll try again before delegating + override def classBytes(name: String): Array[Byte] = findAbstractFile(classNameToPath(name)) match { + case null => super.classBytes(name) + case file => file.toByteArray + } + override def findClass(name: String): Class[_] = { + val bytes = classBytes(name) + if (bytes.length == 0) + throw new ClassNotFoundException(name) + else + defineClass(name, bytes, 0, bytes.length, protectionDomain) + } + + lazy val protectionDomain = { + val cl = Thread.currentThread().getContextClassLoader() + val resource = cl.getResource("scala/runtime/package.class") + if (resource == null || resource.getProtocol != "jar") null else { + val s = resource.getPath + val n = s.lastIndexOf('!') + if (n < 0) null else { + val path = s.substring(0, n) + new ProtectionDomain(new CodeSource(new URL(path), null.asInstanceOf[Array[Certificate]]), null, this, null) + } + } + } + + private val packages = mutable.Map[String, Package]() + + override def definePackage(name: String, specTitle: String, specVersion: String, specVendor: String, implTitle: String, implVersion: String, implVendor: String, sealBase: URL): Package = { + throw new UnsupportedOperationException() + } + + override def getPackage(name: String): Package = { + findAbstractDir(name) match { + case null => super.getPackage(name) + case file => packages.getOrElseUpdate(name, { + val ctor = classOf[Package].getDeclaredConstructor(classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[String], classOf[URL], classOf[ClassLoader]) + ctor.setAccessible(true) + ctor.newInstance(name, null, null, null, null, null, null, null, this) + }) + } + } + + override def getPackages(): Array[Package] = + root.iterator.filter(_.isDirectory).map(dir => getPackage(dir.name)).toArray +} diff --git a/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala b/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala new file mode 100644 index 0000000000..a7fd787dfc --- /dev/null +++ b/src/reflect/scala/reflect/internal/util/ScalaClassLoader.scala @@ -0,0 +1,124 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Paul Phillips + */ + +package scala +package reflect.internal.util + +import java.lang.{ ClassLoader => JClassLoader } +import java.lang.reflect.{ Constructor, Modifier, Method } +import java.io.{ File => JFile } +import java.net.{ URLClassLoader => JURLClassLoader } +import java.net.URL +import scala.reflect.runtime.ReflectionUtils.unwrapHandler +import ScalaClassLoader._ +import scala.util.control.Exception.{ catching } +import scala.language.implicitConversions +import scala.reflect.{ ClassTag, classTag } + +trait HasClassPath { + def classPathURLs: Seq[URL] +} + +/** A wrapper around java.lang.ClassLoader to lower the annoyance + * of java reflection. + */ +trait ScalaClassLoader extends JClassLoader { + /** Executing an action with this classloader as context classloader */ + def asContext[T](action: => T): T = { + val saved = contextLoader + try { setContext(this) ; action } + finally setContext(saved) + } + def setAsContext() { setContext(this) } + + /** Load and link a class with this classloader */ + def tryToLoadClass[T <: AnyRef](path: String): Option[Class[T]] = tryClass(path, initialize = false) + /** Load, link and initialize a class with this classloader */ + def tryToInitializeClass[T <: AnyRef](path: String): Option[Class[T]] = tryClass(path, initialize = true) + + private def tryClass[T <: AnyRef](path: String, initialize: Boolean): Option[Class[T]] = + catching(classOf[ClassNotFoundException], classOf[SecurityException]) opt + Class.forName(path, initialize, this).asInstanceOf[Class[T]] + + /** Create an instance of a class with this classloader */ + def create(path: String): AnyRef = + tryToInitializeClass[AnyRef](path) map (_.newInstance()) orNull + + /** 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 => scala.reflect.io.Streamable.bytes(stream) + } + + /** An InputStream representing the given class name, or null if not found. */ + def classAsStream(className: String) = + getResourceAsStream(className.replaceAll("""\.""", "/") + ".class") + + /** Run the main method of a class to be loaded by this classloader */ + def run(objectName: String, arguments: Seq[String]) { + val clsToRun = tryToInitializeClass(objectName) getOrElse ( + throw new ClassNotFoundException(objectName) + ) + val method = clsToRun.getMethod("main", classOf[Array[String]]) + if (!Modifier.isStatic(method.getModifiers)) + throw new NoSuchMethodException(objectName + ".main is not static") + + try asContext(method.invoke(null, Array(arguments.toArray: AnyRef): _*)) // !!! : AnyRef shouldn't be necessary + catch unwrapHandler({ case ex => throw ex }) + } +} + +/** Methods for obtaining various classloaders. + * appLoader: the application classloader. (Also called the java system classloader.) + * extLoader: the extension classloader. + * bootLoader: the boot classloader. + * contextLoader: the context classloader. + */ +object ScalaClassLoader { + /** Returns loaders which are already ScalaClassLoaders unaltered, + * and translates java.net.URLClassLoaders into scala URLClassLoaders. + * Otherwise creates a new wrapper. + */ + implicit def apply(cl: JClassLoader): ScalaClassLoader = cl match { + case cl: ScalaClassLoader => cl + case cl: JURLClassLoader => new URLClassLoader(cl.getURLs.toSeq, cl.getParent) + case _ => new JClassLoader(cl) with ScalaClassLoader + } + def contextLoader = apply(Thread.currentThread.getContextClassLoader) + def appLoader = apply(JClassLoader.getSystemClassLoader) + def setContext(cl: JClassLoader) = + Thread.currentThread.setContextClassLoader(cl) + def savingContextLoader[T](body: => T): T = { + val saved = contextLoader + try body + finally setContext(saved) + } + + class URLClassLoader(urls: Seq[URL], parent: JClassLoader) + extends JURLClassLoader(urls.toArray, parent) + with ScalaClassLoader + with HasClassPath { + + private var classloaderURLs: Seq[URL] = urls + def classPathURLs: Seq[URL] = classloaderURLs + + /** Override to widen to public */ + override def addURL(url: URL) = { + classloaderURLs :+= url + super.addURL(url) + } + } + + def fromURLs(urls: Seq[URL], parent: ClassLoader = null): URLClassLoader = + new URLClassLoader(urls, parent) + + /** True if supplied class exists in supplied path */ + def classExists(urls: Seq[URL], name: String): Boolean = + fromURLs(urls) tryToLoadClass name isDefined + + /** Finding what jar a clazz or instance came from */ + def originOfClass(x: Class[_]): Option[URL] = + Option(x.getProtectionDomain.getCodeSource) flatMap (x => Option(x.getLocation)) +} diff --git a/src/reflect/scala/reflect/runtime/ReflectionUtils.scala b/src/reflect/scala/reflect/runtime/ReflectionUtils.scala index 53495e6ac8..2db9706007 100644 --- a/src/reflect/scala/reflect/runtime/ReflectionUtils.scala +++ b/src/reflect/scala/reflect/runtime/ReflectionUtils.scala @@ -8,6 +8,7 @@ package reflect.runtime import java.lang.{Class => jClass} import java.lang.reflect.{ Method, InvocationTargetException, UndeclaredThrowableException } +import scala.reflect.internal.util.AbstractFileClassLoader /** A few java-reflection oriented utility functions useful during reflection bootstrapping. */ @@ -34,7 +35,7 @@ private[scala] object ReflectionUtils { def isAbstractFileClassLoader(clazz: Class[_]): Boolean = { if (clazz == null) return false - if (clazz.getName == "scala.tools.nsc.interpreter.AbstractFileClassLoader") return true + if (clazz == classOf[AbstractFileClassLoader]) return true isAbstractFileClassLoader(clazz.getSuperclass) } def inferClasspath(cl: ClassLoader): String = cl match { -- cgit v1.2.3