From 3e8f8ddb2ac057ad82e1a7e37f59baff1c0e15ac Mon Sep 17 00:00:00 2001 From: Raphael Jolly Date: Sat, 9 Mar 2013 21:54:22 +0100 Subject: SI-874 reflect.io improvements --- src/reflect/scala/reflect/io/AbstractFile.scala | 34 ++++++++++---- src/reflect/scala/reflect/io/Path.scala | 9 ++-- src/reflect/scala/reflect/io/ZipArchive.scala | 62 ++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/reflect/scala/reflect/io/AbstractFile.scala b/src/reflect/scala/reflect/io/AbstractFile.scala index 4d6f14c71d..8b69efc749 100644 --- a/src/reflect/scala/reflect/io/AbstractFile.scala +++ b/src/reflect/scala/reflect/io/AbstractFile.scala @@ -7,7 +7,7 @@ package scala.reflect package io -import java.io.{ FileOutputStream, IOException, InputStream, OutputStream, BufferedOutputStream } +import java.io.{ FileOutputStream, IOException, InputStream, OutputStream, BufferedOutputStream, ByteArrayOutputStream } import java.io.{ File => JFile } import java.net.URL import scala.collection.mutable.ArrayBuffer @@ -54,6 +54,8 @@ object AbstractFile { if (url == null || !Path.isExtensionJarOrZip(url.getPath)) null else ZipArchive fromURL url } + + def getResources(url: URL): AbstractFile = ZipArchive fromManifestURL url } /** @@ -156,16 +158,28 @@ abstract class AbstractFile extends Iterable[AbstractFile] { @throws(classOf[IOException]) def toByteArray: Array[Byte] = { val in = input - var rest = sizeOption.getOrElse(0) - val arr = new Array[Byte](rest) - while (rest > 0) { - val res = in.read(arr, arr.length - rest, rest) - if (res == -1) - throw new IOException("read error") - rest -= res + sizeOption match { + case Some(size) => + var rest = size + val arr = new Array[Byte](rest) + while (rest > 0) { + val res = in.read(arr, arr.length - rest, rest) + if (res == -1) + throw new IOException("read error") + rest -= res + } + in.close() + arr + case None => + val out = new ByteArrayOutputStream() + var c = in.read() + while(c != -1) { + out.write(c) + c = in.read() + } + in.close() + out.toByteArray() } - in.close() - arr } /** Returns all abstract subfiles of this abstract directory. */ diff --git a/src/reflect/scala/reflect/io/Path.scala b/src/reflect/scala/reflect/io/Path.scala index 3b5d3079cd..44fb41a1cd 100644 --- a/src/reflect/scala/reflect/io/Path.scala +++ b/src/reflect/scala/reflect/io/Path.scala @@ -56,10 +56,11 @@ object Path { def roots: List[Path] = java.io.File.listRoots().toList map Path.apply def apply(path: String): Path = apply(new JFile(path)) - def apply(jfile: JFile): Path = + def apply(jfile: JFile): Path = try { if (jfile.isFile) new File(jfile) else if (jfile.isDirectory) new Directory(jfile) else new Path(jfile) + } catch { case ex: SecurityException => new Path(jfile) } /** Avoiding any shell/path issues by only using alphanumerics. */ private[io] def randomPrefix = alphanumeric take 6 mkString "" @@ -186,10 +187,10 @@ class Path private[io] (val jfile: JFile) { // Boolean tests def canRead = jfile.canRead() def canWrite = jfile.canWrite() - def exists = jfile.exists() + def exists = try jfile.exists() catch { case ex: SecurityException => false } - def isFile = jfile.isFile() - def isDirectory = jfile.isDirectory() + def isFile = try jfile.isFile() catch { case ex: SecurityException => false } + def isDirectory = try jfile.isDirectory() catch { case ex: SecurityException => false } def isAbsolute = jfile.isAbsolute() def isEmpty = path.length == 0 diff --git a/src/reflect/scala/reflect/io/ZipArchive.scala b/src/reflect/scala/reflect/io/ZipArchive.scala index 5414441e00..1342fde3c5 100644 --- a/src/reflect/scala/reflect/io/ZipArchive.scala +++ b/src/reflect/scala/reflect/io/ZipArchive.scala @@ -7,10 +7,12 @@ package scala.reflect package io import java.net.URL -import java.io.{ IOException, InputStream, ByteArrayInputStream } +import java.io.{ IOException, InputStream, ByteArrayInputStream, FilterInputStream } import java.io.{ File => JFile } import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream } +import java.util.jar.Manifest import scala.collection.{ immutable, mutable } +import scala.collection.convert.WrapAsScala.asScalaIterator import scala.annotation.tailrec /** An abstraction for zip files and streams. Everything is written the way @@ -39,6 +41,8 @@ object ZipArchive { */ def fromURL(url: URL): URLZipArchive = new URLZipArchive(url) + def fromManifestURL(url: URL): AbstractFile = new ManifestResources(url) + private def dirName(path: String) = splitPath(path, front = true) private def baseName(path: String) = splitPath(path, front = false) private def splitPath(path0: String, front: Boolean): String = { @@ -227,3 +231,59 @@ final class URLZipArchive(val url: URL) extends ZipArchive(null) { case _ => false } } + +final class ManifestResources(val url: URL) extends ZipArchive(null) { + def iterator = { + val root = new DirEntry("/") + val dirs = mutable.HashMap[String, DirEntry]("/" -> root) + val manifest = new Manifest(input) + val iter = manifest.getEntries().keySet().iterator().filter(_.endsWith(".class")).map(new ZipEntry(_)) + + while (iter.hasNext) { + val zipEntry = iter.next() + val dir = getDir(dirs, zipEntry) + if (zipEntry.isDirectory) dir + else { + class FileEntry() extends Entry(zipEntry.getName) { + override def lastModified = zipEntry.getTime() + override def input = resourceInputStream(path) + override def sizeOption = None + } + val f = new FileEntry() + dir.entries(f.name) = f + } + } + + try root.iterator + finally dirs.clear() + } + + def name = path + def path: String = url.getPath() match { case s => s.substring(0, s.lastIndexOf('!')) } + def input = url.openStream() + def lastModified = + try url.openConnection().getLastModified() + catch { case _: IOException => 0 } + + override def canEqual(other: Any) = other.isInstanceOf[ManifestResources] + override def hashCode() = url.hashCode + override def equals(that: Any) = that match { + case x: ManifestResources => url == x.url + case _ => false + } + + private def resourceInputStream(path: String): InputStream = { + new FilterInputStream(null) { + override def read(): Int = { + if(in == null) in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + if(in == null) throw new RuntimeException(path + " not found") + super.read(); + } + + override def close(): Unit = { + super.close(); + in = null; + } + } + } +} -- cgit v1.2.3 From 3a30af154e62f5b8a569486b09788c42db2103a6 Mon Sep 17 00:00:00 2001 From: Raphael Jolly Date: Sat, 9 Mar 2013 22:02:02 +0100 Subject: SI-874 actual JSR-223 implementation --- build.xml | 2 + .../scala/tools/nsc/settings/MutableSettings.scala | 4 +- .../tools/nsc/settings/StandardScalaSettings.scala | 1 + .../tools/nsc/util/AbstractFileClassLoader.scala | 15 +- src/compiler/scala/tools/nsc/util/ClassPath.scala | 8 +- src/compiler/scala/tools/util/PathResolver.scala | 2 + src/repl/scala/tools/nsc/interpreter/IMain.scala | 174 ++++++++++++++++++--- 7 files changed, 177 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/build.xml b/build.xml index 7e4948c938..7de9f80f89 100644 --- a/build.xml +++ b/build.xml @@ -1511,9 +1511,11 @@ PACKED QUICK BUILD (PACK) + + diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index 5fa3594128..cc77cbdf52 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -248,11 +248,11 @@ class MutableSettings(val errorFn: String => Unit) private def checkDir(dir: AbstractFile, name: String, allowJar: Boolean = false): AbstractFile = ( if (dir != null && dir.isDirectory) dir -// was: else if (allowJar && dir == null && Path.isJarOrZip(name, false)) else if (allowJar && dir == null && Jar.isJarOrZip(name, examineFile = false)) new PlainFile(Path(name)) else - throw new FatalError(name + " does not exist or is not a directory") +// throw new FatalError(name + " does not exist or is not a directory") + dir ) /** Set the single output directory. From now on, all files will diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala index 9338d9e5b5..d173aaa848 100644 --- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala @@ -45,6 +45,7 @@ trait StandardScalaSettings { val unchecked = BooleanSetting ("-unchecked", "Enable additional warnings where generated code depends on assumptions.") val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.") val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.") + val usemanifestcp = BooleanSetting ("-usemanifestcp", "Utilize the manifest in classpath resolution.") val verbose = BooleanSetting ("-verbose", "Output messages about what the compiler is doing.") val version = BooleanSetting ("-version", "Print product version and exit.") } diff --git a/src/compiler/scala/tools/nsc/util/AbstractFileClassLoader.scala b/src/compiler/scala/tools/nsc/util/AbstractFileClassLoader.scala index 7aef87f387..e4f879560c 100644 --- a/src/compiler/scala/tools/nsc/util/AbstractFileClassLoader.scala +++ b/src/compiler/scala/tools/nsc/util/AbstractFileClassLoader.scala @@ -6,6 +6,9 @@ package scala.tools.nsc package util import scala.tools.nsc.io.AbstractFile +import java.security.cert.Certificate +import java.security.{ ProtectionDomain, CodeSource } +import util.ScalaClassLoader import java.net.{ URL, URLConnection, URLStreamHandler } import scala.collection.{ mutable, immutable } @@ -82,7 +85,17 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) if (bytes.length == 0) throw new ClassNotFoundException(name) else - defineClass(name, bytes, 0, bytes.length) + 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) null else { + val s = resource.getPath + val path = s.substring(0, s.lastIndexOf('!')) + new ProtectionDomain(new CodeSource(new URL(path), null.asInstanceOf[Array[Certificate]]), null, this, null) + } } private val packages = mutable.Map[String, Package]() diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 0104f5a57f..5f13baa107 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -13,6 +13,7 @@ import io.{ File, Directory, Path, Jar, AbstractFile } import scala.reflect.internal.util.StringOps.splitWhere import Jar.isJarOrZip import File.pathSeparator +import scala.collection.convert.WrapAsScala.enumerationAsScalaIterator import java.net.MalformedURLException import java.util.regex.PatternSyntaxException @@ -121,8 +122,13 @@ object ClassPath { private def classesInPathImpl(path: String, expand: Boolean) = for (file <- expandPath(path, expand) ; dir <- Option(AbstractFile getDirectory file)) yield newClassPath(dir) + + def classesInManifest(used: Boolean) = + if (used) for (url <- manifests) yield newClassPath(AbstractFile getResources url) else Nil } + def manifests = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF").filter(_.getProtocol() == "jar").toList + class JavaContext extends ClassPathContext[AbstractFile] { def toBinaryName(rep: AbstractFile) = { val name = rep.name @@ -267,7 +273,7 @@ class SourcePath[T](dir: AbstractFile, val context: ClassPathContext[T]) extends class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[AbstractFile]) extends ClassPath[AbstractFile] { def name = dir.name override def origin = dir.underlyingSource map (_.path) - def asURLs = if (dir.file == null) Nil else List(dir.toURL) + def asURLs = if (dir.file == null) List(new URL(name)) else List(dir.toURL) def asClasspathString = dir.path val sourcepaths: IndexedSeq[AbstractFile] = IndexedSeq() diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala index 9f1d0317be..d8e545e6b1 100644 --- a/src/compiler/scala/tools/util/PathResolver.scala +++ b/src/compiler/scala/tools/util/PathResolver.scala @@ -170,6 +170,7 @@ class PathResolver(settings: Settings, context: JavaContext) { object Calculated { def scalaHome = Defaults.scalaHome def useJavaClassPath = settings.usejavacp.value || Defaults.useJavaClassPath + def useManifestClassPath= settings.usemanifestcp.value def javaBootClassPath = cmdLineOrElse("javabootclasspath", Defaults.javaBootClassPath) def javaExtDirs = cmdLineOrElse("javaextdirs", Defaults.javaExtDirs) def javaUserClassPath = if (useJavaClassPath) Defaults.javaUserClassPath else "" @@ -209,6 +210,7 @@ class PathResolver(settings: Settings, context: JavaContext) { classesInPath(scalaBootClassPath), // 4. The Scala boot class path. contentsOfDirsInPath(scalaExtDirs), // 5. The Scala extension class path. classesInExpandedPath(userClassPath), // 6. The Scala application class path. + classesInManifest(useManifestClassPath), // 8. The Manifest class path. sourcesInPath(sourcePath) // 7. The Scala source path. ) diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index cb2e3c32b8..c92777c13e 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -19,6 +19,11 @@ import scala.tools.nsc.typechecker.{ TypeStrings, StructuredTypeStrings } import ScalaClassLoader.URLClassLoader import scala.tools.nsc.util.Exceptional.unwrap import scala.collection.{ mutable, immutable } +import scala.reflect.BeanProperty +import scala.util.Properties.versionString +import javax.script.{AbstractScriptEngine, Bindings, ScriptContext, ScriptEngine, ScriptEngineFactory, ScriptException, SimpleBindings, CompiledScript, Compilable} +import java.io.{ StringWriter, Reader } +import java.util.Arrays import IMain._ import java.util.concurrent.Future import scala.reflect.runtime.{ universe => ru } @@ -57,7 +62,7 @@ import StdReplTags._ * @author Moez A. Abdel-Gawad * @author Lex Spoon */ -class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends Imports { +class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Settings, protected val out: JPrintWriter) extends AbstractScriptEngine(new SimpleBindings) with Compilable with Imports { imain => object replOutput extends ReplOutput(settings.Yreploutdir) { } @@ -100,7 +105,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } /** construct an interpreter that reports to Console */ + def this(settings: Settings, out: JPrintWriter) = this(null, settings, out) + def this(factory: ScriptEngineFactory, settings: Settings) = this(factory, settings, new NewLinePrintWriter(new ConsoleWriter, true)) def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) + def this(factory: ScriptEngineFactory) = this(factory, new Settings()) def this() = this(new Settings()) lazy val formatting: Formatting = new Formatting { @@ -146,19 +154,9 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } def isInitializeComplete = _initializeComplete - /** the public, go through the future compiler */ lazy val global: Global = { - if (isInitializeComplete) _compiler - else { - // If init hasn't been called yet you're on your own. - if (_isInitialized == null) { - repldbg("Warning: compiler accessed before init set up. Assuming no postInit code.") - initialize(()) - } - // blocks until it is ; false means catastrophic failure - if (_isInitialized.get()) _compiler - else null - } + if (!isInitializeComplete) _initialize() + _compiler } import global._ @@ -535,9 +533,82 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends */ def interpret(line: String): IR.Result = interpret(line, synthetic = false) def interpretSynthetic(line: String): IR.Result = interpret(line, synthetic = true) - def interpret(line: String, synthetic: Boolean): IR.Result = { - def loadAndRunReq(req: Request) = { - classLoader.setAsContext() + def interpret(line: String, synthetic: Boolean): IR.Result = compile(line, synthetic) match { + case Left(result) => result + case Right(req) => new WrappedRequest(req).loadAndRunReq + } + + private def compile(line: String, synthetic: Boolean): Either[IR.Result, Request] = { + if (global == null) Left(IR.Error) + else requestFromLine(line, synthetic) match { + case Left(result) => Left(result) + case Right(req) => + // null indicates a disallowed statement type; otherwise compile and + // fail if false (implying e.g. a type error) + if (req == null || !req.compile) Left(IR.Error) else Right(req) + } + } + + var code = "" + var bound = false + @throws(classOf[ScriptException]) + def compile(script: String): CompiledScript = { + if (!bound) { + quietBind("bindings", getBindings(ScriptContext.ENGINE_SCOPE)) + bound = true + } + val cat = code + script + compile(cat, false) match { + case Left(result) => result match { + case IR.Incomplete => { + code = cat + "\n" + new CompiledScript { + def eval(context: ScriptContext): Object = null + def getEngine: ScriptEngine = IMain.this + } + } + case _ => { + code = "" + throw new ScriptException("compile-time error") + } + } + case Right(req) => { + code = "" + new WrappedRequest(req) + } + } + } + + @throws(classOf[ScriptException]) + def compile(reader: Reader): CompiledScript = { + val writer = new StringWriter() + var c = reader.read() + while(c != -1) { + writer.write(c) + c = reader.read() + } + reader.close() + compile(writer.toString()) + } + + private class WrappedRequest(val req: Request) extends CompiledScript { + var recorded = false + + @throws(classOf[ScriptException]) + def eval(context: ScriptContext): Object = { + val result = req.lineRep.evalEither match { + case Left(e: Exception) => throw new ScriptException(e) + case Left(_) => throw new ScriptException("run-time error") + case Right(result) => result.asInstanceOf[Object] + } + if (!recorded) { + recordRequest(req) + recorded = true + } + result + } + + def loadAndRunReq = classLoader.asContext { val (result, succeeded) = req.loadAndRun /** To our displeasure, ConsoleReporter offers only printMessage, @@ -562,15 +633,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } } - if (global == null) IR.Error - else requestFromLine(line, synthetic) match { - case Left(result) => result - case Right(req) => - // null indicates a disallowed statement type; otherwise compile and - // fail if false (implying e.g. a type error) - if (req == null || !req.compile) IR.Error - else loadAndRunReq(req) - } + def getEngine: ScriptEngine = IMain.this } /** Bind a specified name to a specified value. The name may @@ -703,6 +766,14 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends lazy val evalClass = load(evalPath) + def evalEither = callEither(resultName) match { + case Left(ex) => ex match { + case ex: NullPointerException => Right(null) + case ex => Left(unwrap(ex)) + } + case Right(result) => Right(result) + } + def compile(source: String): Boolean = compileAndSaveRun("", source) /** The innermost object inside the wrapper, found by @@ -738,6 +809,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends mostRecentWarnings = warnings } private def evalMethod(name: String) = evalClass.getMethods filter (_.getName == name) match { + case Array() => null case Array(method) => method case xs => sys.error("Internal error: eval object " + evalClass + ", " + xs.mkString("\n", "\n", "")) } @@ -897,6 +969,16 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends override def toString = "Request(line=%s, %s trees)".format(line, trees.size) } + def createBindings: Bindings = new SimpleBindings + + @throws(classOf[ScriptException]) + def eval(script: String, context: ScriptContext): Object = compile(script).eval(context) + + @throws(classOf[ScriptException]) + def eval(reader: Reader, context: ScriptContext): Object = compile(reader).eval(context) + + override def finalize = close + /** Returns the name of the most recent interpreter result. * Mostly this exists so you can conveniently invoke methods on * the previous result. @@ -1068,6 +1150,48 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends /** Utility methods for the Interpreter. */ object IMain { + class Factory extends ScriptEngineFactory { + @BeanProperty + val engineName = "Scala Interpreter" + + @BeanProperty + val engineVersion = "1.0" + + @BeanProperty + val extensions: JList[String] = Arrays.asList("scala") + + @BeanProperty + val languageName = "Scala" + + @BeanProperty + val languageVersion = versionString + + def getMethodCallSyntax(obj: String, m: String, args: String*): String = null + + @BeanProperty + val mimeTypes: JList[String] = Arrays.asList("application/x-scala") + + @BeanProperty + val names: JList[String] = Arrays.asList("scala") + + def getOutputStatement(toDisplay: String): String = null + + def getParameter(key: String): Object = key match { + case ScriptEngine.ENGINE => engineName + case ScriptEngine.ENGINE_VERSION => engineVersion + case ScriptEngine.LANGUAGE => languageName + case ScriptEngine.LANGUAGE_VERSION => languageVersion + case ScriptEngine.NAME => names.get(0) + case _ => null + } + + def getProgram(statements: String*): String = null + + def getScriptEngine: ScriptEngine = new IMain(this, new Settings() { + usemanifestcp.value = true + }) + } + // The two name forms this is catching are the two sides of this assignment: // // $line3.$read.$iw.$iw.Bippy = -- cgit v1.2.3