diff options
17 files changed, 539 insertions, 408 deletions
diff --git a/src/compiler/scala/tools/ant/ScalaBazaar.scala b/src/compiler/scala/tools/ant/ScalaBazaar.scala index 74f311a376..a787d08365 100644 --- a/src/compiler/scala/tools/ant/ScalaBazaar.scala +++ b/src/compiler/scala/tools/ant/ScalaBazaar.scala @@ -15,7 +15,7 @@ package scala.tools.ant { import scala.collection.mutable.HashMap import java.io.{File, FileInputStream, FileOutputStream, FileWriter, StringReader} - import java.net.{URL, URLClassLoader} + import java.net.URL import java.util.{ArrayList, Vector} import java.util.zip.{ZipOutputStream, ZipEntry} diff --git a/src/compiler/scala/tools/ant/ScalaTool.scala b/src/compiler/scala/tools/ant/ScalaTool.scala index 6917da1287..9fc02859c8 100644 --- a/src/compiler/scala/tools/ant/ScalaTool.scala +++ b/src/compiler/scala/tools/ant/ScalaTool.scala @@ -169,20 +169,17 @@ class ScalaTool extends MatchingTask { private def error(message: String): Nothing = throw new BuildException(message, getLocation()) + // XXX encoding and generalize + private def getResourceAsCharStream(clazz: Class[_], resource: String): Stream[Char] = { + val stream = clazz.getClassLoader() getResourceAsStream resource + if (stream == null) Stream.empty + else Stream continually stream.read() takeWhile (_ != -1) map (_.asInstanceOf[Char]) + } + private def readAndPatchResource(resource: String, tokens: Map[String, String]): String = { - val chars = new Iterator[Char] { - private val stream = - this.getClass().getClassLoader().getResourceAsStream(resource) - private def readStream(): Char = stream.read().asInstanceOf[Char] - private var buf: Char = readStream() - def hasNext: Boolean = (buf != (-1.).asInstanceOf[Char]) - def next: Char = { - val bufbuf = buf - buf = readStream() - bufbuf - } - } + val chars = getResourceAsCharStream(this.getClass, resource).elements val builder = new StringBuilder() + while (chars.hasNext) { val char = chars.next if (char == '@') { @@ -212,13 +209,6 @@ class ScalaTool extends MatchingTask { writer.close() } - private def expandUnixVar(vars: Map[String,String]): Map[String,String] = - vars transform { (x, vari) => vari.replaceAll("#([^#]*)#", "\\$$1") } - - private def expandWinVar(vars: Map[String,String]): Map[String,String] = - vars transform { (x, vari) => vari.replaceAll("#([^#]*)#", "%_$1%") } - - /*============================================================================*\ ** The big execute method ** \*============================================================================*/ diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index e9007c4238..3ede08f40c 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -12,7 +12,7 @@ package scala.tools.ant import java.io.{File,PrintWriter,BufferedWriter,FileWriter} -import org.apache.tools.ant.{BuildException, Project} +import org.apache.tools.ant.{ BuildException, Project, AntClassLoader } import org.apache.tools.ant.taskdefs.{MatchingTask,Java} import org.apache.tools.ant.types.{Path, Reference, FileSet} import org.apache.tools.ant.util.{FileUtils, GlobPatternMapper, @@ -106,6 +106,10 @@ class Scalac extends MatchingTask { * <code>unchecked</code> properties. */ object Flag extends PermissibleValue { val values = List("yes", "no", "on", "off") + def toBoolean(flag: String) = + if (flag == "yes" || flag == "on") Some(true) + else if (flag == "no" || flag == "off") Some(false) + else None } /** The directories that contain source files to compile. */ @@ -164,23 +168,39 @@ class Scalac extends MatchingTask { * (not only the number of files). */ protected var scalacDebugging: Boolean = false + /** Helpers */ + private def setOrAppend(old: Option[Path], arg: Path): Option[Path] = old match { + case Some(x) => x append arg ; Some(x) + case None => Some(arg) + } + private def pathAsList(p: Option[Path], name: String): List[File] = p match { + case None => error("Member '" + name + "' is empty.") + case Some(x) => x.list.toList map nameToFile + } + private def createNewPath(getter: () => Option[Path], setter: (Option[Path]) => Unit) = { + if (getter().isEmpty) + setter(Some(new Path(getProject()))) + + getter().get.createPath() + } + + private def plural(xs: List[Any]) = if (xs.size > 1) "s" else "" + private def plural(x: Int) = if (x > 1) "s" else "" + /*============================================================================*\ ** Properties setters ** \*============================================================================*/ + /** Sets the srcdir attribute. Used by Ant. * @param input The value of <code>origin</code>. */ def setSrcdir(input: Path) { - if (origin.isEmpty) origin = Some(input) - else origin.get.append(input) + origin = setOrAppend(origin, input) } /** Sets the <code>origin</code> as a nested src Ant parameter. * @return An origin path to be configured. */ - def createSrc(): Path = { - if (origin.isEmpty) origin = Some(new Path(getProject())) - origin.get.createPath() - } + def createSrc(): Path = createNewPath(origin _, origin = _) /** Sets the <code>origin</code> as an external reference Ant parameter. * @param input A reference to an origin path. */ @@ -194,20 +214,16 @@ class Scalac extends MatchingTask { /** Sets the <code>classpath</code> attribute. Used by Ant. * @param input The value of <code>classpath</code>. */ def setClasspath(input: Path) { - if (classpath.isEmpty) classpath = Some(input) - else classpath.get.append(input) + classpath = setOrAppend(classpath, input) } /** Sets the <code>compilerPath</code> attribute. Used by Ant. * @param input The value of <code>compilerPath</code>. */ def setCompilerPath(input : Path) { - if(compilerPath.isEmpty) compilerPath = Some(input) - else compilerPath.get.append(input) + compilerPath = setOrAppend(compilerPath, input) } - def createCompilerPath: Path = { - if (compilerPath.isEmpty) compilerPath = Some(new Path(getProject())) - compilerPath.get.createPath() - } + def createCompilerPath: Path = createNewPath(compilerPath _, compilerPath = _) + /** Sets the <code>compilerpathref</code> attribute. Used by Ant. * @param input The value of <code>compilerpathref</code>. */ def setCompilerPathRef(input: Reference) { @@ -216,10 +232,7 @@ class Scalac extends MatchingTask { /** Sets the <code>classpath</code> as a nested classpath Ant parameter. * @return A class path to be configured. */ - def createClasspath(): Path = { - if (classpath.isEmpty) classpath = Some(new Path(getProject())) - classpath.get.createPath() - } + def createClasspath(): Path = createNewPath(classpath _, classpath = _) /** Sets the <code>classpath</code> as an external reference Ant parameter. * @param input A reference to a class path. */ @@ -230,16 +243,12 @@ class Scalac extends MatchingTask { /** Sets the <code>sourcepath</code> attribute. Used by Ant. * @param input The value of <code>sourcepath</code>. */ def setSourcepath(input: Path) { - if (sourcepath.isEmpty) sourcepath = Some(input) - else sourcepath.get.append(input) + sourcepath = setOrAppend(sourcepath, input) } /** Sets the <code>sourcepath</code> as a nested sourcepath Ant parameter. * @return A source path to be configured. */ - def createSourcepath(): Path = { - if (sourcepath.isEmpty) sourcepath = Some(new Path(getProject())) - sourcepath.get.createPath() - } + def createSourcepath(): Path = createNewPath(sourcepath _, sourcepath = _) /** Sets the <code>sourcepath</code> as an external reference Ant parameter. * @param input A reference to a source path. */ @@ -251,17 +260,13 @@ class Scalac extends MatchingTask { * * @param input The value of <code>bootclasspath</code>. */ def setBootclasspath(input: Path) { - if (bootclasspath.isEmpty) bootclasspath = Some(input) - else bootclasspath.get.append(input) + bootclasspath = setOrAppend(bootclasspath, input) } /** Sets the <code>bootclasspath</code> as a nested sourcepath Ant * parameter. * @return A source path to be configured. */ - def createBootclasspath(): Path = { - if (bootclasspath.isEmpty) bootclasspath = Some(new Path(getProject())) - bootclasspath.get.createPath() - } + def createBootclasspath(): Path = createNewPath(bootclasspath _, bootclasspath = _) /** Sets the <code>bootclasspath</code> as an external reference Ant * parameter. @@ -272,15 +277,11 @@ class Scalac extends MatchingTask { /** Sets the external extensions path attribute. Used by Ant. * @param input The value of <code>extdirs</code>. */ def setExtdirs(input: Path) = - if (extdirs.isEmpty) extdirs = Some(input) - else extdirs.get.append(input) + extdirs = setOrAppend(extdirs, input) /** Sets the <code>extdirs</code> as a nested sourcepath Ant parameter. * @return An extensions path to be configured. */ - def createExtdirs(): Path = { - if (extdirs.isEmpty) extdirs = Some(new Path(getProject())) - extdirs.get.createPath() - } + def createExtdirs(): Path = createNewPath(extdirs _, extdirs = _) /** Sets the <code>extdirs</code> as an external reference Ant parameter. * @param input A reference to an extensions path. */ @@ -345,28 +346,19 @@ class Scalac extends MatchingTask { /** Set the <code>deprecation</code> info attribute. * @param input One of the flags <code>yes/no</code> or <code>on/off</code>. */ def setDeprecation(input: String) { - if (Flag.isPermissible(input)) - deprecation = Some("yes" == input || "on" == input) - else - error("Unknown deprecation flag '" + input + "'") + deprecation = Flag toBoolean input orElse error("Unknown deprecation flag '" + input + "'") } /** Set the <code>optimise</code> info attribute. * @param input One of the flags <code>yes/no</code> or <code>on/off</code>. */ def setOptimise(input: String) { - if (Flag.isPermissible(input)) - optimise = Some("yes" == input || "on" == input) - else - error("Unknown optimisation flag '" + input + "'") + optimise = Flag toBoolean input orElse error("Unknown optimisation flag '" + input + "'") } /** Set the <code>unchecked</code> info attribute. * @param input One of the flags <code>yes/no</code> or <code>on/off</code>. */ def setUnchecked(input: String) { - if (Flag.isPermissible(input)) - unchecked = Some("yes" == input || "on" == input) - else - error("Unknown unchecked flag '" + input + "'") + unchecked = Flag toBoolean input orElse error("Unknown unchecked flag '" + input + "'") } /** Sets the <code>force</code> attribute. Used by Ant. @@ -380,7 +372,6 @@ class Scalac extends MatchingTask { def setScalacdebugging(input: Boolean) { scalacDebugging = input } def setAssemname(input: String) { assemname = Some(input) } - def setAssemrefs(input: String) { assemrefs = Some(input) } /*============================================================================*\ @@ -390,16 +381,12 @@ class Scalac extends MatchingTask { /** Gets the value of the <code>classpath</code> attribute in a * Scala-friendly form. * @return The class path as a list of files. */ - protected def getClasspath: List[File] = - if (classpath.isEmpty) error("Member 'classpath' is empty.") - else List.fromArray(classpath.get.list()).map(nameToFile) + protected def getClasspath: List[File] = pathAsList(classpath, "classpath") /** Gets the value of the <code>origin</code> attribute in a * Scala-friendly form. * @return The origin path as a list of files. */ - protected def getOrigin: List[File] = - if (origin.isEmpty) error("Member 'origin' is empty.") - else List.fromArray(origin.get.list()).map(nameToFile) + protected def getOrigin: List[File] = pathAsList(origin, "origin") /** Gets the value of the <code>destination</code> attribute in a * Scala-friendly form. @@ -411,33 +398,22 @@ class Scalac extends MatchingTask { /** Gets the value of the <code>sourcepath</code> attribute in a * Scala-friendly form. * @return The source path as a list of files. */ - protected def getSourcepath: List[File] = - if (sourcepath.isEmpty) error("Member 'sourcepath' is empty.") - else List.fromArray(sourcepath.get.list()).map(nameToFile) + protected def getSourcepath: List[File] = pathAsList(sourcepath, "sourcepath") /** Gets the value of the <code>bootclasspath</code> attribute in a * Scala-friendly form. * @return The boot class path as a list of files. */ - protected def getBootclasspath: List[File] = - if (bootclasspath.isEmpty) error("Member 'bootclasspath' is empty.") - else List.fromArray(bootclasspath.get.list()).map(nameToFile) + protected def getBootclasspath: List[File] = pathAsList(bootclasspath, "bootclasspath") /** Gets the value of the <code>extdirs</code> attribute in a * Scala-friendly form. * @return The extensions path as a list of files. */ - protected def getExtdirs: List[File] = - if (extdirs.isEmpty) error("Member 'extdirs' is empty.") - else List.fromArray(extdirs.get.list()).map(nameToFile) + protected def getExtdirs: List[File] = pathAsList(extdirs, "extdirs") /*============================================================================*\ ** Compilation and support methods ** \*============================================================================*/ - /** This is forwarding method to circumvent bug #281 in Scala 2. Remove when - * bug has been corrected. */ - override protected def getDirectoryScanner(baseDir: File) = - super.getDirectoryScanner(baseDir) - /** Transforms a string name into a file relative to the provided base * directory. * @param base A file pointing to the location relative to which the name @@ -503,59 +479,52 @@ class Scalac extends MatchingTask { protected def initialize: (Settings, List[File], Boolean) = { // Tests if all mandatory attributes are set and valid. if (origin.isEmpty) error("Attribute 'srcdir' is not set.") - if (getOrigin.isEmpty) error("Attribute 'srcdir' is not set.") if (!destination.isEmpty && !destination.get.isDirectory()) error("Attribute 'destdir' does not refer to an existing directory.") if (destination.isEmpty) destination = Some(getOrigin.head) val mapper = new GlobPatternMapper() - mapper.setTo("*.class") - mapper.setFrom("*.scala") + mapper setTo "*.class" + mapper setFrom "*.scala" var javaOnly = true + def getOriginFiles(originDir: File) = { + val includedFiles = getDirectoryScanner(originDir).getIncludedFiles() + val javaFiles = includedFiles filter (_ endsWith ".java") + val scalaFiles = { + val xs = includedFiles filter (_ endsWith ".scala") + if (force) xs + else new SourceFileScanner(this).restrict(xs, originDir, destination.get, mapper) + } + + javaOnly = javaOnly && (scalaFiles.length == 0) + val list = (scalaFiles ++ javaFiles).toList + + if (scalacDebugging && !list.isEmpty) + log("Compiling source file%s: %s to %s".format( + plural(list), + list.mkString(", "), + getDestination.toString + )) + else if (!list.isEmpty) { + val str = + if (javaFiles.isEmpty) "%d source file%s".format(list.length, plural(list)) + else "%d scala and %d java source files".format(scalaFiles.length, javaFiles.length) + log("Compiling %s to %s".format(str, getDestination.toString)) + } + else log("No files selected for compilation", Project.MSG_VERBOSE) + + list + } + // Scans source directories to build up a compile lists. // If force is false, only files were the .class file in destination is // older than the .scala file will be used. val sourceFiles: List[File] = - for { - val originDir <- getOrigin - val originFiles <- { - val includedFiles = getDirectoryScanner(originDir).getIncludedFiles() - var scalaFiles = includedFiles.filter(_.endsWith(".scala")) - val javaFiles = includedFiles.filter(_.endsWith(".java")) - if (!force) { - scalaFiles = new SourceFileScanner(this). - restrict(scalaFiles, originDir, destination.get, mapper) - } - javaOnly = javaOnly && (scalaFiles.length == 0) - val list = scalaFiles.toList ::: javaFiles.toList - if (scalacDebugging && list.length > 0) - log( - list.mkString( - "Compiling source file" + - (if (list.length > 1) "s: " else ": "), - ", ", - " " - ) + "to " + getDestination.toString - ) - else if (list.length > 0) - log( - "Compiling " + ( - if (javaFiles.length > 0) - (scalaFiles.length +" scala and "+ javaFiles.length +" java source files") - else - (list.length +" source file"+ (if (list.length > 1) "s" else "")) - ) +" to "+ getDestination.toString - ) - else - log("No files selected for compilation", Project.MSG_VERBOSE) - list - } - } - yield { - log(originFiles, Project.MSG_DEBUG) - nameToFile(originDir)(originFiles) + for (originDir <- getOrigin ; originFile <- getOriginFiles(originDir)) yield { + log(originFile, Project.MSG_DEBUG) + nameToFile(originDir)(originFile) } // Builds-up the compilation settings for Scalac with the existing Ant @@ -597,115 +566,82 @@ class Scalac extends MatchingTask { override def execute() { val (settings, sourceFiles, javaOnly) = initialize - if (sourceFiles.isEmpty || javaOnly) { + if (sourceFiles.isEmpty || javaOnly) return - } - if(fork) { - //TODO - Error - executeFork(settings, sourceFiles) - } else { - executeInternal(settings, sourceFiles) - } + + if (fork) executeFork(settings, sourceFiles) // TODO - Error + else executeInternal(settings, sourceFiles) } - protected def executeFork(settings: Settings, sourceFiles : List[File]) { - val java = new Java(this) // set this as owner - java.setFork(true) - // using 'setLine' creates multiple arguments out of a space-separated string - for(args <- jvmArgs) { - java.createJvmarg().setLine(args) - } + protected def executeFork(settings: Settings, sourceFiles: List[File]) { + val java = new Java(this) + java setFork true + // using 'setLine' creates multiple arguments out of a space-separated string + jvmArgs foreach { java.createJvmarg() setLine _ } - //Determine the path for scalac! - val scalacPath = { + // use user-provided path or retrieve from classloader + // TODO - Allow user to override the compiler classpath + val scalacPath: Path = { val path = new Path(getProject) - compilerPath match { - case Some(p) => - //Use the user provided path - path.add(p) - case None => - //Pull classpath off our classloader - //TODO - Allow user to override the compiler classpath - import _root_.org.apache.tools.ant.AntClassLoader - val classLoader = getClass.getClassLoader - if(classLoader.isInstanceOf[AntClassLoader]) { - path.add(new Path(getProject, classLoader.asInstanceOf[AntClassLoader].getClasspath)) - } else { - throw new BuildException("Cannot determine default classpath for sclac, please specify one!") - } + if (compilerPath.isDefined) path add compilerPath.get + else getClass.getClassLoader match { + case cl: AntClassLoader => path add new Path(getProject, cl.getClasspath) + case _ => error("Cannot determine default classpath for sclac, please specify one!") } path } - java.setClasspath(scalacPath) - - java.setClassname("scala.tools.nsc.Main") - //if (!timeout.isEmpty) java.setTimeout(timeout.get) + java setClasspath scalacPath + java setClassname "scala.tools.nsc.Main" - //Write all settings to a temporary file + // Write all settings to a temporary file def writeSettings() : File = { def escapeArgument(arg : String) = if(arg.matches(".*\\s.*")) ('"' + arg + '"') else arg val file = File.createTempFile("scalac-ant-",".args") file.deleteOnExit() val out = new PrintWriter(new BufferedWriter(new FileWriter(file))) + try { - for ( setting <- settings.allSettings; - arg <- setting.unparse){ - out.println(escapeArgument(arg)) - } - for (file <- sourceFiles) { - out.println(file.getAbsolutePath) - } - } finally { - out.close(); + for (setting <- settings.allSettings ; arg <- setting.unparse) + out println escapeArgument(arg) + for (file <- sourceFiles) + out println file.getAbsolutePath } + finally out.close() + file } - java.createArg().setValue("@" + writeSettings.getCanonicalPath) - log(java.getCommandLine.getCommandline.mkString("", " ", ""), Project.MSG_VERBOSE) + java.createArg() setValue ("@" + writeSettings.getCanonicalPath) + log(java.getCommandLine.getCommandline.mkString(" "), Project.MSG_VERBOSE) + val res = java.executeJava() if (failonerror && res != 0) error("Compilation failed because of an internal compiler error;"+ " see the error output for details.") - } + /** Performs the compilation. */ protected def executeInternal(settings: Settings, sourceFiles : List[File]) { - - val reporter = new ConsoleReporter(settings) - - // Compiles the actual code - val compiler = newGlobal(settings, reporter) - try { - (new compiler.Run).compile(sourceFiles.map (_.toString)) - } - catch { - case exception: Throwable if (exception.getMessage ne null) => - exception.printStackTrace() - error("Compile failed because of an internal compiler error (" + - exception.getMessage + "); see the error output for details.") - case exception => - exception.printStackTrace() - error("Compile failed because of an internal compiler error " + - "(no error message provided); see the error output for details.") + val compiler = newGlobal(settings, reporter) // compiles the actual code + + try new compiler.Run compile (sourceFiles map (_.toString)) + catch { + case ex: Throwable => + ex.printStackTrace() + val msg = if (ex.getMessage == null) "no error message provided" else ex.getMessage + error("Compile failed because of an internal compiler error (" + msg + "); see the error output for details.") } + reporter.printSummary() if (reporter.hasErrors) { - val msg = - "Compile failed with " + - reporter.ERROR.count + " error" + - (if (reporter.ERROR.count > 1) "s" else "") + - "; see the compiler error output for details." + val msg = "Compile failed with %d error%s; see the compiler error output for details.".format( + reporter.ERROR.count, plural(reporter.ERROR.count)) if (failonerror) error(msg) else log(msg) } else if (reporter.WARNING.count > 0) - log( - "Compile suceeded with " + - reporter.WARNING.count + " warning" + - (if (reporter.WARNING.count > 1) "s" else "") + - "; see the compiler output for details.") + log("Compile succeeded with %d warning%s; see the compiler output for details.".format( + reporter.WARNING.count, plural(reporter.WARNING.count))) } - } diff --git a/src/compiler/scala/tools/ant/sabbus/Compiler.scala b/src/compiler/scala/tools/ant/sabbus/Compiler.scala index cd5b4a88aa..787c6af870 100644 --- a/src/compiler/scala/tools/ant/sabbus/Compiler.scala +++ b/src/compiler/scala/tools/ant/sabbus/Compiler.scala @@ -13,25 +13,19 @@ package scala.tools.ant.sabbus import java.io.File import java.net.URL import java.lang.reflect.InvocationTargetException +import scala.util.ScalaClassLoader -class Compiler(classpath: Array[URL], val settings: Settings) { - - private lazy val classLoader: ClassLoader = - new java.net.URLClassLoader(classpath, null) - - private lazy val foreignCompilerName: String = - "scala.tools.ant.sabbus.ForeignCompiler" - private lazy val foreignCompiler: AnyRef = - classLoader.loadClass(foreignCompilerName).newInstance.asInstanceOf[AnyRef] +class Compiler(classpath: Array[URL], val settings: Settings) +{ + val foreignCompilerName: String = "scala.tools.ant.sabbus.ForeignCompiler" + private lazy val classLoader = ScalaClassLoader fromURLs classpath + private lazy val foreignCompiler: AnyRef = classLoader create foreignCompilerName private def settingsArray: Array[String] = settings.toArgs.toArray - foreignInvoke("args_$eq", Array(classOf[Array[String]]), Array(settingsArray)) - private def foreignInvoke(method: String, types: Array[Class[T] forSome { type T }] , args: Array[AnyRef]) = - try { - foreignCompiler.getClass.getMethod(method, types : _*).invoke(foreignCompiler, args : _*) - } + private def foreignInvoke(method: String, types: Array[Class[_]], args: Array[AnyRef]) = + try foreignCompiler.getClass.getMethod(method, types: _*).invoke(foreignCompiler, args: _*) catch { case e: InvocationTargetException => throw e.getCause } @@ -44,8 +38,6 @@ class Compiler(classpath: Array[URL], val settings: Settings) { (result >> 16, result & 0x00FF) } catch { - case ex: Exception => - throw CompilationFailure(ex.getMessage, ex) + case ex: Exception => throw CompilationFailure(ex.getMessage, ex) } - } diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 0fbd115f4d..453f96120e 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -8,13 +8,15 @@ package scala.tools.nsc import java.io.{ File, PrintWriter, StringWriter, Writer } import java.lang.{ Class, ClassLoader } -import java.net.{ MalformedURLException, URL, URLClassLoader } +import java.net.{ MalformedURLException, URL } import java.lang.reflect import reflect.InvocationTargetException import scala.collection.immutable.ListSet import scala.collection.mutable import scala.collection.mutable.{ ListBuffer, HashSet, ArrayBuffer } +import scala.util.{ ScalaClassLoader, URLClassLoader } +import scala.util.control.Exception.reflectionUnwrapper import io.{ PlainFile, VirtualDirectory } import reporters.{ ConsoleReporter, Reporter } @@ -70,7 +72,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) val virtualDirectory = new VirtualDirectory("(memory)", None) /** the compiler to compile expressions with */ - val compiler: nsc.Global = newCompiler(settings, reporter) + val compiler: Global = newCompiler(settings, reporter) import compiler.{ Traverser, CompilationUnit, Symbol, Name, Type } import compiler.{ @@ -112,18 +114,15 @@ class Interpreter(val settings: Settings, out: PrintWriter) /** Instantiate a compiler. Subclasses can override this to * change the compiler class used by this interpreter. */ protected def newCompiler(settings: Settings, reporter: Reporter) = { - settings.outputDirs.setSingleOutput(virtualDirectory) - val comp = new nsc.Global(settings, reporter) - comp + settings.outputDirs setSingleOutput virtualDirectory + new Global(settings, reporter) } /** the compiler's classpath, as URL's */ val compilerClasspath: List[URL] = { + import net.Utility.parseURL val classpathPart = ClassPath.expandPath(compiler.settings.classpath.value).map(s => new File(s).toURL) - def parseURL(s: String): Option[URL] = - try { Some(new URL(s)) } - catch { case _: MalformedURLException => None } val codebasePart = (compiler.settings.Xcodebase.value.split(" ")).toList flatMap parseURL classpathPart ::: codebasePart @@ -142,58 +141,49 @@ class Interpreter(val settings: Settings, out: PrintWriter) shadow the old ones, and old code objects refer to the old definitions. */ - private var classLoader = makeClassLoader() - private def makeClassLoader(): ClassLoader = { - val cp = compilerClasspath.toArray + private var classLoader: ScalaClassLoader = makeClassLoader() + private def makeClassLoader(): ScalaClassLoader = { val parent = - if (parentClassLoader == null) new URLClassLoader(cp) - else new URLClassLoader(cp, parentClassLoader) + if (parentClassLoader == null) ScalaClassLoader fromURLs compilerClasspath + else new URLClassLoader(compilerClasspath, parentClassLoader) new AbstractFileClassLoader(virtualDirectory, parent) } + private def loadByName(s: String): Class[_] = (classLoader tryToInitializeClass s).get + private def methodByName(c: Class[_], name: String): reflect.Method = + c.getMethod(name, classOf[Object]) - private def loadByName(s: String): Class[_] = Class.forName(s, true, classLoader) - // XXX how does this differ from getMethod("set") ? - private def methodByName(c: Class[_], name: String): Option[reflect.Method] = - c.getDeclaredMethods.toList.find(_.getName == name) + protected def parentClassLoader: ClassLoader = this.getClass.getClassLoader() // Set the current Java "context" class loader to this interpreter's class loader - def setContextClassLoader() = Thread.currentThread.setContextClassLoader(classLoader) - - protected def parentClassLoader: ClassLoader = this.getClass.getClassLoader() + def setContextClassLoader() = classLoader.setAsContext() /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() private def allUsedNames = prevRequests.toList.flatMap(_.usedNames).removeDuplicates private def allBoundNames = prevRequests.toList.flatMap(_.boundNames).removeDuplicates - /** counter creator */ - def mkNameCreator(s: String) = new Function0[String] with Function1[String,String] { + /** Generates names pre0, pre1, etc. via calls to apply method */ + class NameCreator(pre: String) { private var x = -1 - def apply(): String = { x += 1 ; s + x.toString } - // second apply method temp for newInternalVarName's bug compatibility - def apply(pre: String) = { x += 1 ; pre + x.toString } + def apply(): String = { x += 1 ; pre + x.toString } def reset(): Unit = x = -1 + def didGenerate(name: String) = + (name startsWith pre) && ((name drop pre.length) forall (_.isDigit)) } /** allocate a fresh line name */ - private val newLineName = mkNameCreator(INTERPRETER_LINE_PREFIX) + private val lineNameCreator = new NameCreator(INTERPRETER_LINE_PREFIX) /** allocate a fresh var name */ - private val newVarName = mkNameCreator(INTERPRETER_VAR_PREFIX) + private val varNameCreator = new NameCreator(INTERPRETER_VAR_PREFIX) /** allocate a fresh internal variable name */ - /** XXX temporarily shares newVarName's creator to be bug-compatible with - * test case interpreter.scala */ - private def newInternalVarName = () => newVarName(INTERPRETER_SYNTHVAR_PREFIX) - // private val newInternalVarName = mkNameCreator(INTERPRETER_SYNTHVAR_PREFIX) + private def synthVarNameCreator = new NameCreator(INTERPRETER_SYNTHVAR_PREFIX) - private def isGenerated(pre: String, name: String) = - (name startsWith pre) && (name drop pre.length).forall(_.isDigit) - - /** Check if a name looks like it was generated by newVarName */ - private def isGeneratedVarName(name: String): Boolean = isGenerated(INTERPRETER_VAR_PREFIX, name) - private def isSynthVarName(name: String): Boolean = isGenerated(INTERPRETER_SYNTHVAR_PREFIX, name) + /** Check if a name looks like it was generated by varNameCreator */ + private def isGeneratedVarName(name: String): Boolean = varNameCreator didGenerate name + private def isSynthVarName(name: String): Boolean = synthVarNameCreator didGenerate name /** generate a string using a routine that wants to write on a stream */ private def stringFrom(writer: PrintWriter => Unit): String = { @@ -424,12 +414,12 @@ class Interpreter(val settings: Settings, out: PrintWriter) if (trees.size == 1) trees.head match { case _:Assign => // we don't want to include assignments case _:TermTree | _:Ident | _:Select => - return interpret("val %s =\n%s".format(newVarName(), line)) + return interpret("val %s =\n%s".format(varNameCreator(), line)) case _ => } // figure out what kind of request - val req = buildRequest(trees, line, newLineName()) + val req = buildRequest(trees, line, lineNameCreator()) // null is a disallowed statement type; otherwise compile and fail if false (implying e.g. a type error) if (req == null || !req.compile) return IR.Error @@ -446,7 +436,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) } /** A name creator used for objects created by <code>bind()</code>. */ - private val newBinder = mkNameCreator("binder") + private val newBinder = new NameCreator("binder") /** Bind a specified name to a specified value. The name may * later be used by expressions passed to interpret. @@ -467,7 +457,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) """.stripMargin.format(binderName, boundType, boundType)) val binderObject = loadByName(binderName) - val setterMethod = methodByName(binderObject, "set").get + val setterMethod = methodByName(binderObject, "set") // this roundabout approach is to ensure the value is boxed var argsHolder: Array[Any] = null @@ -480,8 +470,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) def reset() { virtualDirectory.clear classLoader = makeClassLoader - newLineName.reset() - newVarName.reset() + lineNameCreator.reset() + varNameCreator.reset() prevRequests.clear } @@ -572,7 +562,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) private class AssignHandler(member: Assign) extends MemberHandler(member) { val lhs = member.lhs.asInstanceOf[Ident] // an unfortunate limitation - val helperName = newTermName(newInternalVarName()) + val helperName = newTermName(synthVarNameCreator()) override val valAndVarNames = List(helperName) override def extraCodeToEvaluate(req: Request, code: PrintWriter) = @@ -773,16 +763,11 @@ class Interpreter(val settings: Settings, out: PrintWriter) def loadAndRun: (String, Boolean) = { val resultObject: Class[_] = loadByName(resultObjectName) val resultValMethod: reflect.Method = resultObject getMethod "result" + lazy val pair = (resultValMethod.invoke(resultObject).toString, true) - try { (resultValMethod.invoke(resultObject).toString, true) } - catch { case e => - // unwrap unhelpful nested exceptions so the most interesting one is reported - def unwrap(e: Throwable): Throwable = e match { - case (_: InvocationTargetException | _: ExceptionInInitializerError) if e.getCause ne null => - unwrap(e.getCause) - case _ => e - } - (stringFrom(unwrap(e).printStackTrace(_)), false) + (reflectionUnwrapper either pair) match { + case Left(e) => (stringFrom(e.printStackTrace(_)), false) + case Right((res, success)) => (res, success) } } } @@ -870,11 +855,11 @@ class Interpreter(val settings: Settings, out: PrintWriter) val res = beQuietDuring { for (name <- nameOfIdent(line) ; req <- requestForName(name)) yield { - if (interpret("val " + newInternalVarName() + " = " + name + methodsCode) != IR.Success) Nil + if (interpret("val " + synthVarNameCreator() + " = " + name + methodsCode) != IR.Success) Nil else { val result = prevRequests.last.resultObjectName - val resultObj = Class.forName(result, true, classLoader) - val valMethod = resultObj.getMethod("result") + val resultObj = (classLoader tryToInitializeClass result).get + val valMethod = resultObj getMethod "result" val str = valMethod.invoke(resultObj).toString str.substring(str.indexOf('=') + 1).trim . @@ -896,10 +881,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) filter(!isSynthVarName(_)) /** For static/object method completion */ - def tryToLoadClass(path: String): Option[Class[_]] = { - try { Some(Class.forName(path, false, classLoader)) } - catch { case _: ClassNotFoundException | _: NoClassDefFoundError => None } - } + def getClassObject(path: String): Option[Class[_]] = classLoader tryToLoadClass path // debugging private var debuggingOutput = false diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index 15a57729fc..2beeec28e4 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -8,7 +8,6 @@ package scala.tools.nsc import java.io.{BufferedReader, File, FileReader, PrintWriter} import java.io.IOException -import java.lang.{ClassLoader, System} import scala.tools.nsc.{InterpreterResults => IR} import scala.tools.nsc.interpreter._ @@ -84,6 +83,9 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { var interpreter: Interpreter = _ // set by createInterpreter() def isettings = interpreter.isettings + // XXX + var addedClasspath: List[String] = Nil + /** A reverse list of commands to replay if the user requests a :replay */ var replayCommandsRev: List[String] = Nil @@ -104,6 +106,9 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { /** Create a new interpreter. */ def createInterpreter() { + if (!addedClasspath.isEmpty) + settings.classpath.value += addedClasspath.map(File.pathSeparator + _).mkString + interpreter = new Interpreter(settings, out) { override protected def parentClassLoader = classOf[InterpreterLoop].getClassLoader } @@ -155,6 +160,7 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { import CommandImplicits._ List( NoArgs("help", "prints this help message", printHelp), + OneArg("jar", "add a jar to the classpath", addJar), OneArg("load", "followed by a filename loads a Scala file", load), NoArgs("power", "enable power user mode", power), NoArgs("quit", "exits the interpreter", () => Result(false, None)), @@ -262,6 +268,18 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { Result(true, shouldReplay) } + + def addJar(arg: String): Unit = { + val f = new java.io.File(arg) + if (!f.exists) { + out.println("The file '" + f + "' doesn't seem to exist.") + return + } + addedClasspath = addedClasspath ::: List(f.getCanonicalPath) + println("Added " + f.getCanonicalPath + " to your classpath.") + replay() + } + def power() = { powerUserOn = true interpreter.powerUser() diff --git a/src/compiler/scala/tools/nsc/MainGenericRunner.scala b/src/compiler/scala/tools/nsc/MainGenericRunner.scala index 3d7b5ac7ee..55967222c1 100644 --- a/src/compiler/scala/tools/nsc/MainGenericRunner.scala +++ b/src/compiler/scala/tools/nsc/MainGenericRunner.scala @@ -10,7 +10,8 @@ package scala.tools.nsc import java.io.{File, IOException} import java.lang.{ClassNotFoundException, NoSuchMethodException} import java.lang.reflect.InvocationTargetException -import java.net.URL +import java.net.{ URL, MalformedURLException } +import scala.util.ScalaClassLoader import util.ClassPath import File.pathSeparator @@ -25,40 +26,23 @@ object MainGenericRunner { * input classpath is empty; otherwise do not. * * @param classpath - * @return ... + * @return the new classpath */ private def addClasspathExtras(classpath: String): String = { val scalaHome = Properties.scalaHome - val extraClassPath = - if (scalaHome eq null) - "" - else { - def listDir(name:String):Array[File] = { - val libdir = new File(new File(scalaHome), name) - if (!libdir.exists || libdir.isFile) - Array() - else - libdir.listFiles - } - { - val filesInLib = listDir("lib") - val jarsInLib = - filesInLib.filter(f => - f.isFile && f.getName.endsWith(".jar")) - jarsInLib.toList - } ::: { - val filesInClasses = listDir("classes") - val dirsInClasses = - filesInClasses.filter(f => f.isDirectory) - dirsInClasses.toList - } - }.mkString("", pathSeparator, "") - - if (classpath == "") - extraClassPath + pathSeparator + "." - else - classpath + pathSeparator + extraClassPath + def listDir(name: String): List[File] = { + val libdir = new File(new File(scalaHome), name) + if (!libdir.exists || libdir.isFile) Nil else libdir.listFiles.toList + } + lazy val jarsInLib = listDir("lib") filter (_.getName endsWith ".jar") + lazy val dirsInClasses = listDir("classes") filter (_.isDirectory) + val cpScala = + if (scalaHome == null) Nil + else (jarsInLib ::: dirsInClasses) map (_.toString) + + // either prepend existing classpath or append "." + (if (classpath == "") cpScala ::: List(".") else classpath :: cpScala) mkString pathSeparator } def main(args: Array[String]) { @@ -114,8 +98,8 @@ object MainGenericRunner { ) yield url.get def specToURL(spec: String): Option[URL] = - try { Some(new URL(spec)) } - catch { case e => Console.println(e); None } + try { Some(new URL(spec)) } + catch { case e: MalformedURLException => Console.println(e); None } def urls(specs: String): List[URL] = if (specs == null || specs.length == 0) Nil @@ -146,8 +130,7 @@ object MainGenericRunner { settings.howtorun.value match { case "object" => true case "script" => false - case "guess" => - ObjectRunner.classExists(classpath, thingToRun) + case "guess" => ScalaClassLoader.classExists(classpath, thingToRun) } if (isObjectName) { diff --git a/src/compiler/scala/tools/nsc/ObjectRunner.scala b/src/compiler/scala/tools/nsc/ObjectRunner.scala index 58da7b16eb..e4e0826d32 100644 --- a/src/compiler/scala/tools/nsc/ObjectRunner.scala +++ b/src/compiler/scala/tools/nsc/ObjectRunner.scala @@ -7,9 +7,8 @@ package scala.tools.nsc -import java.lang.{Class, ClassNotFoundException, NoSuchMethodException} -import java.lang.reflect.{Method, Modifier} -import java.net.{URL, URLClassLoader} +import java.net.URL +import scala.util.ScalaClassLoader /** An object that runs another object specified by name. * @@ -18,57 +17,10 @@ import java.net.{URL, URLClassLoader} */ object ObjectRunner { - // we cannot use the app classloader here or we get what looks to - // be classloader deadlock, but if we pass null we bypass the extension - // classloader and our extensions, so we search the hierarchy to find - // the classloader whose parent is null. Resolves bug #857. - private def findExtClassLoader(): ClassLoader = { - def search(cl: ClassLoader): ClassLoader = { - if (cl == null) null - else if (cl.getParent == null) cl - else search(cl.getParent) - } - - search(Thread.currentThread.getContextClassLoader) - } - - /** Create a class loader for the specified class path */ - private def makeClassLoader(classpath: List[URL]): URLClassLoader = - makeClassLoader(classpath, findExtClassLoader()) - private def makeClassLoader(classpath: List[URL], parent: ClassLoader): URLClassLoader = - new URLClassLoader(classpath.toArray, parent) - - /** Look up a class with a given class path. */ - private def findClass(loader: ClassLoader, objectName: String) - : Option[Class[T] forSome { type T }] = - { - try { - Some(Class.forName(objectName, true, loader)) - } catch { - case e: SecurityException => - Console.println(e.getMessage) - None - case _: ClassNotFoundException => - None - } - } - /** Check whether a class with the specified name * exists on the specified class path. */ - def classExists(classpath: List[URL], objectName: String): Boolean = - !findClass(makeClassLoader(classpath), objectName).isEmpty - - /** Set the Java context class loader while executing an action */ - def withContextClassLoader[T](loader: ClassLoader)(action: =>T): T = { - val oldLoader = Thread.currentThread.getContextClassLoader - try { - Thread.currentThread.setContextClassLoader(loader) - action - } finally { - Thread.currentThread.setContextClassLoader(oldLoader) - } - } - + def classExists(urls: List[URL], objectName: String): Boolean = + ScalaClassLoader.classExists(urls, objectName) /** Run a given object, specified by name, using a * specified classpath and argument list. @@ -77,19 +29,7 @@ object ObjectRunner * @throws NoSuchMethodError * @throws InvocationTargetException */ - def run(classpath: List[URL], objectName: String, arguments: Seq[String]) { - val loader = makeClassLoader(classpath) - val clsToRun = findClass(loader, objectName) match { - case Some(cls) => cls - case None => throw new ClassNotFoundException(objectName) - } - - val method = clsToRun.getMethod("main", classOf[Array[String]]) - if ((method.getModifiers & Modifier.STATIC) == 0) - throw new NoSuchMethodException(objectName + ".main is not static") - - withContextClassLoader(loader) { - method.invoke(null, List(arguments.toArray).toArray: _*) - } + def run(urls: List[URL], objectName: String, arguments: Seq[String]) { + (ScalaClassLoader fromURLs urls).run(objectName, arguments) } } diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala index cefbecee81..5d96a54f64 100644 --- a/src/compiler/scala/tools/nsc/Settings.scala +++ b/src/compiler/scala/tools/nsc/Settings.scala @@ -753,7 +753,6 @@ trait ScalacSettings val noCompletion = BooleanSetting ("-Yno-completion", "Disable tab-completion in the REPL") val Xdce = BooleanSetting ("-Ydead-code", "Perform dead code elimination") val debug = BooleanSetting ("-Ydebug", "Output debugging messages") - val debugger = BooleanSetting ("-Ydebugger", "Enable interactive debugger") val Xdetach = BooleanSetting ("-Ydetach", "Perform detaching of remote closures") // val doc = BooleanSetting ("-Ydoc", "Generate documentation") val inline = BooleanSetting ("-Yinline", "Perform inlining when possible") diff --git a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala index bb1d049677..964fd3f377 100644 --- a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala @@ -6,6 +6,7 @@ package scala.tools.nsc.interpreter import scala.tools.nsc.io.AbstractFile +import scala.util.ScalaClassLoader /** * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. @@ -13,21 +14,18 @@ import scala.tools.nsc.io.AbstractFile * @author Lex Spoon */ class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) -extends ClassLoader(parent) + extends ClassLoader(parent) + with ScalaClassLoader { override def findClass(name: String): Class[_] = { + def onull[T](x: T): T = if (x == null) throw new ClassNotFoundException(name) else x var file: AbstractFile = root val pathParts = name.split("[./]").toList - for (dirPart <- pathParts.init) { - file = file.lookupName(dirPart, true) - if (file == null) { - throw new ClassNotFoundException(name) - } - } - file = file.lookupName(pathParts.last+".class", false) - if (file == null) { - throw new ClassNotFoundException(name) - } + + for (dirPart <- pathParts.init) + file = onull(file.lookupName(dirPart, true)) + + file = onull(file.lookupName(pathParts.last+".class", false)) val bytes = file.toByteArray defineClass(name, bytes, 0, bytes.length) } diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index 2a6362568a..27259e0d2e 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -136,9 +136,9 @@ class Completion(val interpreter: Interpreter) extends Completor { filter (isValidCompletion) // java style, static methods - val js = interpreter.tryToLoadClass(path).map(getMembers(_, true)) getOrElse Nil + val js = (interpreter getClassObject path).map(getMembers(_, true)) getOrElse Nil // scala style, methods on companion object - val ss = interpreter.tryToLoadClass(path + "$").map(getMembers(_, false)) getOrElse Nil + val ss = (interpreter getClassObject (path + "$")).map(getMembers(_, false)) getOrElse Nil js ::: ss } diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala b/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala index 3559b1f5b1..9fd93ab6ff 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala @@ -108,9 +108,9 @@ abstract class SymbolTable extends Names } } - /** Break into repl debugger if assertion is true and debugging enabled */ + /** Break into repl debugger if assertion is true */ def breakIf(assertion: => Boolean, args: Any*): Unit = - if (settings.debugger.value && assertion) + if (assertion) Interpreter.break(args.toList) /** The set of all installed infotransformers */ diff --git a/src/library/scala/net/Utility.scala b/src/library/scala/net/Utility.scala new file mode 100644 index 0000000000..033dcc86a3 --- /dev/null +++ b/src/library/scala/net/Utility.scala @@ -0,0 +1,19 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2009, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.net + +import java.net.{ URL, MalformedURLException } +import scala.util.control.Exception._ + +/** Skeleton in anticipation of more convenience methods. */ +object Utility +{ + def parseURL(s: String): Option[URL] = + catching(classOf[MalformedURLException]) opt new URL(s) +}
\ No newline at end of file diff --git a/src/library/scala/util/ClassLoader.scala b/src/library/scala/util/ClassLoader.scala new file mode 100644 index 0000000000..503fdc7202 --- /dev/null +++ b/src/library/scala/util/ClassLoader.scala @@ -0,0 +1,109 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2009 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.util + +import java.lang.{ ClassLoader => JavaClassLoader } +import java.lang.reflect.{ Modifier, Method } +import java.net.URL +import ScalaClassLoader._ +import scala.util.control.Exception.{ catching } + +trait ScalaClassLoader extends JavaClassLoader +{ + /** Executing an action with this classloader as context classloader */ + def asContext[T](action: => T): T = { + val oldLoader = getContextLoader + try { + setContextLoader(this) + action + } + finally setContextLoader(oldLoader) + } + def setAsContext() { setContextLoader(this) } + + /** Load and link a class with this classloader */ + def tryToLoadClass[T <: AnyRef](path: String): Option[Class[T]] = tryClass(path, false) + /** Load, link and initialize a class with this classloader */ + def tryToInitializeClass[T <: AnyRef](path: String): Option[Class[T]] = tryClass(path, 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(path) match { + case Some(clazz) => clazz.newInstance() + case None => null + } + } + + /** 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") + + asContext(method.invoke(null, Array(arguments.toArray): _*)) + } +} + +class URLClassLoader(urls: List[URL], parent: JavaClassLoader) + extends java.net.URLClassLoader(urls.toArray, parent) + with ScalaClassLoader +{ + /** Override to widen to public */ + override def addURL(url: URL) = super.addURL(url) +} + +object ScalaClassLoader { + def setContextLoader(cl: JavaClassLoader) = Thread.currentThread.setContextClassLoader(cl) + def getContextLoader() = Thread.currentThread.getContextClassLoader() + def getSystemLoader() = JavaClassLoader.getSystemClassLoader() + def defaultParentClassLoader() = findExtClassLoader() + + /** XXX move this to RichClass. */ + def callReflectively[T](clazz: Class[_], obj: String, method: String, args: Any*): Option[T] = { + val exceptions = List( + classOf[ClassNotFoundException], + classOf[NoSuchMethodException], + classOf[SecurityException], + classOf[NullPointerException], + classOf[ClassCastException] + ) + + catching(exceptions: _*) opt { + val o: Class[_] = clazz.getClassLoader loadClass obj + val m: Method = o getDeclaredMethod method + m.invoke(o, args map (_.asInstanceOf[AnyRef]) : _*).asInstanceOf[T] + } + } + + def fromURLs(urls: Seq[URL]): URLClassLoader = + new URLClassLoader(urls.toList, defaultParentClassLoader()) + + /** True if supplied class exists in supplied path */ + def classExists(urls: Seq[URL], name: String): Boolean = + (fromURLs(urls) tryToLoadClass name).isDefined + + // we cannot use the app classloader here or we get what looks to + // be classloader deadlock, but if we pass null we bypass the extension + // classloader and our extensions, so we search the hierarchy to find + // the classloader whose parent is null. Resolves bug #857. + def findExtClassLoader(): JavaClassLoader = { + def search(cl: JavaClassLoader): JavaClassLoader = { + if (cl == null) null + else if (cl.getParent == null) cl + else search(cl.getParent) + } + + search(getContextLoader()) + } +}
\ No newline at end of file diff --git a/src/library/scala/util/control/Exception.scala b/src/library/scala/util/control/Exception.scala new file mode 100644 index 0000000000..bd01886c12 --- /dev/null +++ b/src/library/scala/util/control/Exception.scala @@ -0,0 +1,166 @@ +package scala.util.control + +/** Classes representing the components of exception handling. + * Each class is independently composable. Some common uses: + * + * <pre> + * <b>import</b> scala.util.control.Exception._ + * <b>import</b> java.net._ + * + * <b>val</b> x1 = catching(classOf[MalformedURLException]) opt new URL(s) + * <b>val</b> x2 = catching(classOf[MalformedURLException], classOf[NullPointerException]) either new URL(s) + * </pre> + * @author Paul Phillips + */ + +import java.lang.reflect.InvocationTargetException + +object Exception +{ + trait Described { + protected val name: String + private var _desc: String = "" + def desc = _desc + def withDesc(s: String): this.type = { + _desc = s + this + } + override def toString() = name + "(" + desc + ")" + } + + /** A container class for finally code. */ + class Finally(fin: => Unit) extends Described { + protected val name = "Finally" + + def butFirst(fin2: => Unit) = new Finally({ fin2 ; fin }) + def andAlso(fin2: => Unit) = new Finally({ fin ; fin2 }) + def invoke() { fin } + } + + /** A container class for catch logic. */ + class Catch[+T](val pf: PartialFunction[Throwable, T]) extends Described { + protected val name = "Catch" + + /** Create a new Catch with additional exception handling logic. */ + def orElse[U >: T](pf2: PartialFunction[Throwable, U]): Catch[U] = new Catch(pf orElse pf2) + def orElse[U >: T](catch2: Catch[U]): Catch[U] = orElse(catch2.pf) + def orElse[U >: T](res: U, exes: Class[_ <: Throwable]*): Catch[U] = + orElse(pfFromExceptionsWithResult(_ => res, exes : _*)) + + /** Invoke this catch logic upon the supplied body. */ + def invokeOn[U >: T](body: => U): U = + try { body } + catch { case e if pf.isDefinedAt(e) => pf(e) } + + /** Invoke this catch logic upon the supplied body, mapping the result + * into Option[T] - None if any exception was caught, Some(T) otherwise. + */ + def opt[U >: T](body: => U): Option[U] = toOption invokeOn Some(body) + + /** Invoke this catch logic upon the supplied body, mapping the result + * into Either[Throwable, T] - Left(exception) if an exception was caught, + * Right(T) otherwise. + */ + def either[U >: T](body: => U): Either[Throwable, U] = toEither invokeOn Right(body) + + /** Create a new Catch with the same isDefinedAt logic as this one, + * but with the supplied apply method replacing the current one. */ + def withApply[U](f: (Throwable) => U): Catch[U] = { + val pf2 = new PartialFunction[Throwable, U] { + def isDefinedAt(x: Throwable) = pf isDefinedAt x + def apply(x: Throwable) = f(x) + } + new Catch(pf2) + } + + /** Convenience methods. */ + def toOption: Catch[Option[T]] = withApply(_ => None) + def toEither: Catch[Either[Throwable, T]] = withApply(Left(_)) + } + + /** A container class for try/catch/finally logic. */ + class Try[+T](body: => T, val catcher: Catch[T], val fin: Finally) { + /** Invoke "body" using catch logic "catcher" and finally "fin" */ + def invoke(): T = withFin { catcher invokeOn body } + + /** As invoke, but map caught exceptions to None and success to Some(T) */ + def opt(): Option[T] = withFin { catcher opt body } + + /** As invoke, but map caught exceptions to Left(ex) and success to Right(x) */ + def either(): Either[Throwable, T] = withFin { catcher either body } + + /** Create a new Try with the supplied body replacing the current body */ + def tryInstead[U >: T](body2: => U) = new Try(body2, catcher, fin) + + /** Create a new Try with the supplied Catch replacing the current Catch */ + def catchInstead[U >: T](catcher2: Catch[U]) = new Try(body, catcher2, fin) + + /** Create a new Try with the supplied Finally replacing the current Finally */ + def finInstead(fin2: Finally) = new Try(body, catcher, fin2) + + /** Create a new Try with the supplied logic appended to the existing Catch logic. */ + def catchAlso[U >: T](pf2: PartialFunction[Throwable, U]) = + new Try(body, catcher orElse pf2, fin) + + /** Create a new Try with the supplied code appended to the existing Finally. */ + def finAlso(fin2: => Unit) = new Try(body, catcher, fin andAlso fin2) + + override def toString() = List("Try(<body>)", catcher.toString, fin.toString) mkString " " + private def withFin[T](f: => T) = + try { f } + finally { fin.invoke() } + } + + /** The empty Catch object. */ + final val noCatch = new Catch( + new PartialFunction[Throwable, Nothing] { + def isDefinedAt(x: Throwable) = false + def apply(x: Throwable) = throw x + } + ) withDesc "<nothing>" + + /** The empty Finally object. */ + final val noFinally = new Finally(()) withDesc "()" + + /** Creates a Catch object which will catch any of the supplied exceptions. + * Since the returned Catch object has no specific logic defined and will simply + * rethrow the exceptions it catches, you will typically want to call "opt" or + * "either" on the return value, or assign custom logic by calling "withApply". + */ + def catching[T](exceptions: Class[_ <: Throwable]*): Catch[T] = + new Catch(pfFromExceptions(exceptions : _*)) withDesc exceptions.map(_.getName).mkString(", ") + + /** Create a Try object with the supplied body and Catch logic. */ + def tryCatch[T](body: => T)(pf: PartialFunction[Throwable, T]) = + new Try(body, new Catch(pf), noFinally) + + /** Create a Try object with the supplied body and Finally code. */ + def tryFin[T](body: => T)(fin: => Unit) = + new Try(body, noCatch, new Finally(fin)) + + /** Create a Try object with the supplied body, Catch logic, and Finally code. */ + def tryCatchFin[T](body: => T)(pf: PartialFunction[Throwable, T])(fin: => Unit) = + new Try(body, new Catch(pf), new Finally(fin)) + + val reflectionUnwrapper: Catch[Nothing] = { + // unwrap unhelpful nested exceptions so the most interesting one is reported + def unwrap(e: Throwable): Throwable = e match { + case (_: InvocationTargetException | _: ExceptionInInitializerError) if e.getCause ne null => + unwrap(e.getCause) + case _ => e + } + + new Catch({ case e => throw unwrap(e) }) + } + + /** Private **/ + + private def pfFromExceptionsWithResult[T](f: (Throwable) => T, exceptions: Class[_ <: Throwable]*) = + new PartialFunction[Throwable, T] { + def apply(ex: Throwable) = f(ex) + def isDefinedAt(x: Throwable) = exceptions exists (_ isAssignableFrom x.getClass) + } + + private def pfFromExceptions(exceptions: Class[_ <: Throwable]*) = + pfFromExceptionsWithResult[Nothing](throw _, exceptions : _*) +} diff --git a/src/partest/scala/tools/partest/nest/CompileManager.scala b/src/partest/scala/tools/partest/nest/CompileManager.scala index d33eee1ccd..f7612823fc 100644 --- a/src/partest/scala/tools/partest/nest/CompileManager.scala +++ b/src/partest/scala/tools/partest/nest/CompileManager.scala @@ -157,9 +157,9 @@ class ReflectiveCompiler(val fileManager: ConsoleFileManager) extends SimpleComp val fileClass = Class.forName("java.io.File") val stringClass = Class.forName("java.lang.String") val sepCompileMethod = - sepCompilerClass.getMethod("compile", Array(fileClass, stringClass): _*) + sepCompilerClass.getMethod("compile", fileClass, stringClass) val sepCompileMethod2 = - sepCompilerClass.getMethod("compile", Array(fileClass, stringClass, fileClass): _*) + sepCompilerClass.getMethod("compile", fileClass, stringClass, fileClass) /* This method throws java.lang.reflect.InvocationTargetException * if the compiler crashes. @@ -167,8 +167,7 @@ class ReflectiveCompiler(val fileManager: ConsoleFileManager) extends SimpleComp * methods of class CompileManager. */ def compile(out: Option[File], files: List[File], kind: String, log: File): Boolean = { - val fileArgs: Array[AnyRef] = Array(out, files, kind, log) - val res = sepCompileMethod2.invoke(sepCompiler, fileArgs: _*).asInstanceOf[java.lang.Boolean] + val res = sepCompileMethod2.invoke(sepCompiler, out, files, kind, log).asInstanceOf[java.lang.Boolean] res.booleanValue() } } diff --git a/test/files/jvm/interpreter.check b/test/files/jvm/interpreter.check index cd55922604..2e18987d0f 100644 --- a/test/files/jvm/interpreter.check +++ b/test/files/jvm/interpreter.check @@ -171,7 +171,7 @@ scala> <console>:1: error: expected start of definition scala> scala> scala> -scala> | | | | res3: scala.xml.Elem = +scala> | | | | res2: scala.xml.Elem = <a> <b d="dd" c="c"></b></a> @@ -181,7 +181,7 @@ scala> | | | | scala> scala> scala> -scala> | | | res4: java.lang.String = +scala> | | | res3: java.lang.String = hello there |