diff options
Diffstat (limited to 'src/compiler')
18 files changed, 1295 insertions, 911 deletions
diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala index 010d183bc8..426562509d 100644 --- a/src/compiler/scala/tools/nsc/CompileServer.scala +++ b/src/compiler/scala/tools/nsc/CompileServer.scala @@ -6,10 +6,9 @@ package scala.tools.nsc -import java.io.{BufferedOutputStream, File, FileOutputStream, PrintStream} -import java.lang.{Runtime, System, Thread} +import java.io.{ BufferedOutputStream, FileOutputStream, PrintStream, File => JFile } +import io.File -import scala.concurrent.ops.spawn import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} import scala.tools.nsc.util.FakePos //Position import scala.tools.util.SocketServer @@ -22,7 +21,8 @@ import scala.tools.util.SocketServer * @author Martin Odersky * @version 1.0 */ -class StandardCompileServer extends SocketServer { +class StandardCompileServer extends SocketServer +{ def compileSocket: CompileSocket = CompileSocket // todo: make this a lazy val val versionMsg = "Fast Scala compiler " + @@ -44,6 +44,7 @@ class StandardCompileServer extends SocketServer { } private val runtime = Runtime.getRuntime() + import runtime.{ totalMemory, freeMemory, maxMemory } var reporter: ConsoleReporter = _ @@ -54,24 +55,31 @@ class StandardCompileServer extends SocketServer { } override def timeout() { - if (!compileSocket.portFile(port).exists()) + if (!compileSocket.portFile(port).exists) fatal("port file no longer exists; skipping cleanup") } + def printMemoryStats() { + System.out.println("New session, total memory = %s, max memory = %s, free memory = %s".format( + totalMemory, maxMemory, freeMemory)) + System.out.flush() + } + + def isMemoryFullEnough() = { + runtime.gc() + (totalMemory - freeMemory).toDouble / maxMemory.toDouble > MaxCharge + } + protected def newOfflineCompilerCommand( arguments: List[String], settings: Settings, error: String => Unit, - interactive: Boolean) - = new OfflineCompilerCommand(arguments, settings, error, interactive) + interactive: Boolean + ) = new OfflineCompilerCommand(arguments, settings, error, interactive) def session() { - System.out.println("New session" + - ", total memory = "+ runtime.totalMemory() + - ", max memory = " + runtime.maxMemory() + - ", free memory = " + runtime.freeMemory) - System.out.flush() - val password = compileSocket.getPassword(port) + printMemoryStats() + val password = compileSocket getPassword port val guessedPassword = in.readLine() val input = in.readLine() if ((input ne null) && password == guessedPassword) { @@ -127,24 +135,17 @@ class StandardCompileServer extends SocketServer { shutDown = true } reporter.printSummary() - runtime.gc() - if ((runtime.totalMemory() - runtime.freeMemory()).toDouble / - runtime.maxMemory().toDouble > MaxCharge) compiler = null + if (isMemoryFullEnough) + compiler = null } } } /** A directory holding redirected output */ - private val redirectDir = new File(compileSocket.tmpDir, "output-redirects") - redirectDir.mkdirs - - private def redirect(setter: PrintStream => Unit, filename: String) { - setter( - new PrintStream( - new BufferedOutputStream( - new FileOutputStream( - new File(redirectDir, filename))))) - } + private val redirectDir = (compileSocket.tmpDir / "output-redirects").createDirectory() + + private def redirect(setter: PrintStream => Unit, filename: String): Unit = + setter(new PrintStream((redirectDir / filename).createFile().bufferedOutput())) def main(args: Array[String]) { redirect(System.setOut, "scala-compile-server-out.log") diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala index 8cb679f24b..03c86ec962 100644 --- a/src/compiler/scala/tools/nsc/CompileSocket.scala +++ b/src/compiler/scala/tools/nsc/CompileSocket.scala @@ -6,12 +6,16 @@ package scala.tools.nsc -import java.lang.{Thread, System, Runtime} -import java.lang.NumberFormatException -import java.io.{File, IOException, PrintWriter, FileOutputStream} -import java.io.{BufferedReader, FileReader} +import java.io.{ IOException, FileNotFoundException, PrintWriter, FileOutputStream } +import java.io.{ BufferedReader, FileReader } import java.util.regex.Pattern import java.net._ +import java.security.SecureRandom + +import io.{ File, Path } +import scala.util.control.Exception.catching + +// class CompileChannel { } /** This class manages sockets for the fsc offline compiler. */ class CompileSocket { @@ -25,17 +29,13 @@ class CompileSocket { protected def cmdName = Properties.cmdName //todo: lazy val /** The vm part of the command to start a new scala compile server */ - protected val vmCommand = - Properties.scalaHome match { - case null => - cmdName - case dirname => - val trial = new File(new File(dirname, "bin"), cmdName) - if (trial.canRead) - trial.getPath - else - cmdName - } + protected val vmCommand = Properties.scalaHome match { + case null => cmdName + case dirname => + val trial = File(dirname) / "bin" / cmdName + if (trial.canRead) trial.path + else cmdName + } /** The class name of the scala compile server */ protected val serverClass = "scala.tools.nsc.CompileServer" @@ -44,7 +44,7 @@ class CompileSocket { val errorRegex = ".*(errors? found|don't know|bad option).*" /** A Pattern object for checking compiler output for errors */ - val errorPattern = Pattern.compile(errorRegex) + val errorPattern = Pattern compile errorRegex protected def error(msg: String) = System.err.println(msg) @@ -59,8 +59,7 @@ class CompileSocket { /** A temporary directory to use */ val tmpDir = { val udir = Option(Properties.userName) getOrElse "shared" - val f = new File(Properties.tmpDir, "scala-devel/" + udir) - f.mkdirs() + val f = (Path(Properties.tmpDir) / "scala-devel" / udir).createDirectory() if (f.isDirectory && f.canWrite) { info("[Temp directory: " + f + "]") @@ -70,8 +69,7 @@ class CompileSocket { } /* A directory holding port identification files */ - val portsDir = new File(tmpDir, dirName) - portsDir.mkdirs + val portsDir = (tmpDir / dirName).createDirectory() /** Maximum number of polls for an available port */ private val MaxAttempts = 100 @@ -103,24 +101,16 @@ class CompileSocket { } /** The port identification file */ - def portFile(port: Int) = new File(portsDir, port.toString()) + def portFile(port: Int) = portsDir / File(port.toString) /** Poll for a server port number; return -1 if none exists yet */ - private def pollPort(): Int = { - val hits = portsDir.listFiles() - if (hits.length == 0) -1 - else - try { - for (i <- 1 until hits.length) hits(i).delete() - hits(0).getName.toInt - } catch { - case ex: NumberFormatException => - fatal(ex.toString() + - "\nbad file in temp directory: " + - hits(0).getAbsolutePath() + - "\nplease remove the file and try again") - } - } + private def pollPort(): Int = + portsDir.list.toList match { + case Nil => -1 + case p :: xs => + xs forall (_.delete()) + p.name.toInt + } /** Get the port number to which a scala compile server is connected; * If no server is running yet, then create one. @@ -131,6 +121,7 @@ class CompileSocket { if (port < 0) startNewServer(vmArgs) + while (port < 0 && attempts < MaxAttempts) { attempts += 1 Thread.sleep(sleepTime) @@ -144,25 +135,23 @@ class CompileSocket { /** Set the port number to which a scala compile server is connected */ def setPort(port: Int) { - try { - val f = new PrintWriter(new FileOutputStream(portFile(port))) - f.println(new java.security.SecureRandom().nextInt.toString) - f.close() - } catch { - case ex: /*FileNotFound+Security*/Exception => - fatal("Cannot create file: " + - portFile(port).getAbsolutePath()) + val file = portFile(port) + val secret = new SecureRandom().nextInt.toString + + try file writeAll List(secret) catch { + case e @ (_: FileNotFoundException | _: SecurityException) => + fatal("Cannot create file: %s".format(file.path)) } } /** Delete the port number to which a scala compile server was connected */ - def deletePort(port: Int) { portFile(port).delete() } + def deletePort(port: Int) = portFile(port).delete() /** Get a socket connected to a daemon. If create is true, then * create a new daemon if necessary. Returns null if the connection * cannot be established. */ - def getOrCreateSocket(vmArgs: String, create: Boolean): Socket = { + def getOrCreateSocket(vmArgs: String, create: Boolean = true): Socket = { val nAttempts = 49 // try for about 5 seconds def getsock(attempts: Int): Socket = if (attempts == 0) { @@ -193,45 +182,40 @@ class CompileSocket { getsock(nAttempts) } - /** Same as getOrCreateSocket(vmArgs, true). */ - def getOrCreateSocket(vmArgs: String): Socket = - getOrCreateSocket(vmArgs, true) + // XXX way past time for this to be central + def parseInt(x: String): Option[Int] = + try { Some(x.toInt) } + catch { case _: NumberFormatException => None } def getSocket(serverAdr: String): Socket = { - val cpos = serverAdr indexOf ':' - if (cpos < 0) - fatal("Malformed server address: " + serverAdr + "; exiting") - else { - val hostName = serverAdr.substring(0, cpos) - val port = try { - serverAdr.substring(cpos+1).toInt - } catch { - case ex: Throwable => - fatal("Malformed server address: " + serverAdr + "; exiting") - } - getSocket(hostName, port) + def fail = fatal("Malformed server address: %s; exiting" format serverAdr) + (serverAdr indexOf ':') match { + case -1 => fail + case cpos => + val hostName: String = serverAdr take cpos + parseInt(serverAdr drop (cpos + 1)) match { + case Some(port) => getSocket(hostName, port) + case _ => fail + } } } def getSocket(hostName: String, port: Int): Socket = - try { - new Socket(hostName, port) - } catch { - case e: /*IO+Security*/Exception => - fatal("Unable to establish connection to server " + - hostName + ":" + port + "; exiting") + try new Socket(hostName, port) catch { + case e @ (_: IOException | _: SecurityException) => + fatal("Unable to establish connection to server %s:%d; exiting".format(hostName, port)) } def getPassword(port: Int): String = { - val ff = portFile(port) - val f = new BufferedReader(new FileReader(ff)) + val ff = portFile(port) + val f = ff.bufferedReader() + // allow some time for the server to start up - var retry = 50 - while (ff.length() == 0 && retry > 0) { - Thread.sleep(100) - retry -= 1 + def check = { + Thread sleep 100 + ff.length } - if (ff.length() == 0) { + if (Iterator continually check take 50 find (_ > 0) isEmpty) { ff.delete() fatal("Unable to establish connection to server.") } diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 3f44bcdf9b..d34c4a8640 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -957,15 +957,6 @@ object Interpreter { intLoop.closeInterpreter } - /** Delete a directory tree recursively. Use with care! */ - private[nsc] def deleteRecursively(path: File): Unit = - if (path.exists) { - if (path.isDirectory) - path.listFiles foreach deleteRecursively - - path.delete - } - /** Heuristically strip interpreter wrapper prefixes * from an interpreter output string. */ diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index e31dcd38f9..f87fdf093d 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -6,14 +6,19 @@ package scala.tools.nsc -import java.io.{BufferedReader, File, FileInputStream, FileOutputStream, - FileReader, InputStreamReader, PrintWriter, - FileWriter, IOException} +import java.io.{ + InputStream, OutputStream, + BufferedReader, FileInputStream, FileOutputStream, + FileReader, InputStreamReader, PrintWriter, FileWriter, + IOException +} +import java.io.{ File => JFile } +import io.{ Directory, File, Path, PlainFile } import java.lang.reflect.InvocationTargetException import java.net.URL -import java.util.jar.{JarEntry, JarOutputStream} +import java.util.jar.{ JarEntry, JarOutputStream } +import java.util.regex.Pattern -import scala.tools.nsc.io.PlainFile import scala.tools.nsc.reporters.{Reporter,ConsoleReporter} import scala.tools.nsc.util.{ClassPath, CompoundSourceFile, BatchSourceFile, SourceFile, SourceFileFragment} @@ -43,136 +48,123 @@ import scala.tools.nsc.util.{ClassPath, CompoundSourceFile, BatchSourceFile, Sou * @todo It would be better if error output went to stderr instead * of stdout... */ -object ScriptRunner { +object ScriptRunner +{ + /* While I'm chasing down the fsc and script bugs. */ + def DBG(msg: Any) { + System.err.println(msg.toString) + System.err.flush() + } + /** Default name to use for the wrapped script */ val defaultScriptMain = "Main" + private def addShutdownHook(body: => Unit) = + Runtime.getRuntime addShutdownHook new Thread { override def run { body } } + /** Pick a main object name from the specified settings */ - def scriptMain(settings: Settings) = - if (settings.script.value == "") - defaultScriptMain - else - settings.script.value - - /** Choose a jar filename to hold the compiled version - * of a script - */ + def scriptMain(settings: Settings) = settings.script.value match { + case "" => defaultScriptMain + case x => x + } + + /** Choose a jar filename to hold the compiled version of a script. */ private def jarFileFor(scriptFile: String): File = { - val filename = - if (scriptFile.matches(".*\\.[^.\\\\/]*")) - scriptFile.replaceFirst("\\.[^.\\\\/]*$", ".jar") - else - scriptFile + ".jar" + val name = + if (scriptFile endsWith ".jar") scriptFile + else scriptFile + ".jar" + + File(name) + } + + def copyStreams(in: InputStream, out: OutputStream) = { + val buf = new Array[Byte](10240) + + def loop: Unit = in.read(buf, 0, buf.length) match { + case -1 => in.close() + case n => out.write(buf, 0, n) ; loop + } - new File(filename) + loop } /** Try to create a jar file out of all the contents * of the directory <code>sourcePath</code>. */ - private def tryMakeJar(jarFile: File, sourcePath: File) = { - try { - val jarFileStream = new FileOutputStream(jarFile) - val jar = new JarOutputStream(jarFileStream) - val buf = new Array[Byte](10240) - - def addFromDir(dir: File, prefix: String) { - for (entry <- dir.listFiles) { - if (entry.isFile) { - jar.putNextEntry(new JarEntry(prefix + entry.getName)) - - val input = new FileInputStream(entry) - var n = input.read(buf, 0, buf.length) - while (n >= 0) { - jar.write (buf, 0, n) - n = input.read(buf, 0, buf.length) - } - jar.closeEntry - input.close - } else { - addFromDir(entry, prefix + entry.getName + "/") - } - } + private def tryMakeJar(jarFile: File, sourcePath: Directory) = { + def addFromDir(jar: JarOutputStream, dir: Directory, prefix: String) { + def addFileToJar(entry: File) = { + jar putNextEntry new JarEntry(prefix + entry.name) + copyStreams(entry.inputStream, jar) + jar.closeEntry + } + + dir.list foreach { entry => + if (entry.isFile) addFileToJar(entry.toFile) + else addFromDir(jar, entry.toDirectory, prefix + entry.name + "/") } + } - addFromDir(sourcePath, "") + try { + val jar = new JarOutputStream(jarFile.outputStream()) + addFromDir(jar, sourcePath, "") jar.close - } catch { - case _:Error => jarFile.delete // XXX what errors to catch? + } + catch { + case _: Error => jarFile.delete() // XXX what errors to catch? } } - /** Read the entire contents of a file as a String. */ - private def contentsOfFile(filename: String): String = { - val strbuf = new StringBuilder - val reader = new FileReader(filename) - val cbuf = new Array[Char](1024) - while(true) { - val n = reader.read(cbuf) - if (n <= 0) - return strbuf.toString - strbuf.append(cbuf, 0, n) - } - throw new Error("impossible") - } + private def contentsOfFile(filename: String) = File(filename).slurp() /** Find the length of the header in the specified file, if * there is one. The header part starts with "#!" or "::#!" * and ends with a line that begins with "!#" or "::!#". */ private def headerLength(filename: String): Int = { - import java.util.regex._ - + val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE) val fileContents = contentsOfFile(filename) + def isValid = List("#!", "::#!") exists (fileContents startsWith _) - if (!(fileContents.startsWith("#!") || fileContents.startsWith("::#!"))) - return 0 - - val matcher = - (Pattern.compile("^(::)?!#.*(\\r|\\n|\\r\\n)", Pattern.MULTILINE) - .matcher(fileContents)) - if (!matcher.find) - throw new IOException("script file does not close its header with !# or ::!#") - return matcher.end + if (!isValid) 0 else { + val matcher = headerPattern matcher fileContents + if (matcher.find) matcher.end + else throw new IOException("script file does not close its header with !# or ::!#") + } } /** Split a fully qualified object name into a * package and an unqualified object name */ - private def splitObjectName(fullname: String): - (Option[String],String) = - { - val idx = fullname.lastIndexOf('.') - if (idx < 0) - (None, fullname) - else - (Some(fullname.substring(0,idx)), fullname.substring(idx+1)) - } + private def splitObjectName(fullname: String): (Option[String], String) = + (fullname lastIndexOf '.') match { + case -1 => (None, fullname) + case idx => (Some(fullname take idx), fullname drop (idx + 1)) + } /** Code that is added to the beginning of a script file to make * it a complete Scala compilation unit. */ - protected def preambleCode(objectName: String) = { - val (maybePack, objName) = splitObjectName(objectName) - - val packageDecl = - maybePack match { - case Some(pack) => "package " + pack + "\n" - case None => "" - } - - (packageDecl + - "object " + objName + " {\n" + - " def main(argv: Array[String]): Unit = {\n" + - " val args = argv;\n" + - " new AnyRef {\n") + protected def preambleCode(objectName: String): String = { + val (maybePack, objName) = splitObjectName(objectName) + val packageDecl = maybePack map ("package %s\n" format _) getOrElse ("") + + return """| + | object %s { + | def main(argv: Array[String]): Unit = { + | val args = argv + | new AnyRef { + |""".stripMargin.format(objName) } /** Code that is added to the end of a script file to make * it a complete Scala compilation unit. */ - val endCode = "\n} \n} }\n" - + val endCode = """ + | } + | } + | } + |""".stripMargin /** Wrap a script file into a runnable object named * <code>scala.scripting.Main</code>. @@ -180,20 +172,12 @@ object ScriptRunner { def wrappedScript( objectName: String, filename: String, - getSourceFile: PlainFile => SourceFile): SourceFile = + getSourceFile: PlainFile => BatchSourceFile): SourceFile = { - val preamble = - new BatchSourceFile("<script preamble>", - preambleCode(objectName).toCharArray) - + val preamble = new BatchSourceFile("<script preamble>", preambleCode(objectName).toCharArray) val middle = { - val f = new File(filename) - val bsf = getSourceFile(new PlainFile(f)).asInstanceOf[BatchSourceFile] - new SourceFileFragment( - bsf, - headerLength(filename), - bsf.length) -// f.length.asInstanceOf[Int]) + val bsf = getSourceFile(PlainFile fromPath filename) + new SourceFileFragment(bsf, headerLength(filename), bsf.length) } val end = new BatchSourceFile("<script trailer>", endCode.toCharArray) @@ -210,53 +194,42 @@ object ScriptRunner { settings: GenericRunnerSettings, scriptFileIn: String): Boolean = { - val scriptFile = CompileClient.absFileName(scriptFileIn) - for (setting <- List( - settings.classpath, - settings.sourcepath, - settings.bootclasspath, - settings.extdirs, - settings.outdir)) - setting.value = CompileClient.absFileNames(setting.value) - - val compSettingNames = - (new Settings(error)).allSettings.map(_.name) - - val compSettings = - settings.allSettings.filter(stg => - compSettingNames.contains(stg.name)) - - val coreCompArgs = - compSettings.foldLeft[List[String]](Nil)((args, stg) => - stg.unparse ::: args) - - val compArgs = - (coreCompArgs ::: - List("-Xscript", scriptMain(settings), scriptFile)) - - val socket = CompileSocket.getOrCreateSocket("") - if (socket eq null) - return false - - val out = new PrintWriter(socket.getOutputStream(), true) - val in = new BufferedReader(new InputStreamReader(socket.getInputStream())) + val scriptFile = CompileClient absFileName scriptFileIn - out.println(CompileSocket.getPassword(socket.getPort)) - out.println(compArgs.mkString("", "\0", "")) - - var compok = true - - var fromServer = in.readLine() - while (fromServer ne null) { - Console.err.println(fromServer) - if (CompileSocket.errorPattern.matcher(fromServer).matches) - compok = false + { + import settings._ + for (setting <- List(classpath, sourcepath, bootclasspath, extdirs, outdir)) { + // DBG("%s = %s".format(setting.name, setting.value)) + setting.value = CompileClient absFileName setting.value + } + } - fromServer = in.readLine() + val compSettingNames = new Settings(error).allSettings map (_.name) + val compSettings = settings.allSettings filter (compSettingNames contains _.name) + val coreCompArgs = compSettings flatMap (_.unparse) + val compArgs = coreCompArgs ::: List("-Xscript", scriptMain(settings), scriptFile) + var compok = true + + // XXX temporary as I started using ManagedResource not remembering it wasn't checked in. + def ManagedResource[T](x: => T) = Some(x) + + for { + socket <- ManagedResource(CompileSocket getOrCreateSocket "") + val _ = if (socket == null) return false + out <- ManagedResource(new PrintWriter(socket.getOutputStream(), true)) + in <- ManagedResource(new BufferedReader(new InputStreamReader(socket.getInputStream()))) + } { + out println (CompileSocket getPassword socket.getPort) + out println (compArgs mkString "\0") + + for (fromServer <- (Iterator continually in.readLine()) takeWhile (_ != null)) { + Console.err println fromServer + if (CompileSocket.errorPattern matcher fromServer matches) + compok = false + } + // XXX temp until managed resource is available + in.close() ; out.close() ; socket.close() } - in.close() - out.close() - socket.close() compok } @@ -269,101 +242,80 @@ object ScriptRunner { * * @returns true if compilation and the handler succeeds, false otherwise. */ - private def withCompiledScript - (settings: GenericRunnerSettings, scriptFile: String) - (handler: String => Boolean) - : Boolean = { - import Interpreter.deleteRecursively - - /** Compiles the script file, and returns - * the directory with the compiled class files, - * if the compilation succeeded. - */ - def compile: Option[File] = { - val compiledPath = File.createTempFile("scalascript", "") - compiledPath.delete // the file is created as a file; make it a directory - compiledPath.mkdirs + private def withCompiledScript( + settings: GenericRunnerSettings, + scriptFile: String) + (handler: String => Boolean): Boolean = + { + /** Compiles the script file, and returns the directory with the compiled + * class files, if the compilation succeeded. + */ + def compile: Option[Directory] = { + val compiledPath = Directory makeTemp "scalascript" // delete the directory after the user code has finished - Runtime.getRuntime.addShutdownHook(new Thread { - override def run { deleteRecursively(compiledPath) }}) + addShutdownHook(compiledPath.deleteRecursively()) - settings.outdir.value = compiledPath.getPath + settings.outdir.value = compiledPath.path if (settings.nocompdaemon.value) { val reporter = new ConsoleReporter(settings) val compiler = newGlobal(settings, reporter) val cr = new compiler.Run - val wrapped = - wrappedScript( - scriptMain(settings), - scriptFile, - compiler.getSourceFile _) - cr.compileSources(List(wrapped)) - if (!reporter.hasErrors) - Some(compiledPath) - else - None - } else { - if (compileWithDaemon(settings, scriptFile)) - Some(compiledPath) - else - None + val wrapped = wrappedScript(scriptMain(settings), scriptFile, compiler getSourceFile _) + + cr compileSources List(wrapped) + if (reporter.hasErrors) None else Some(compiledPath) } + else if (compileWithDaemon(settings, scriptFile)) Some(compiledPath) + else None } if (settings.savecompiled.value) { val jarFile = jarFileFor(scriptFile) + def jarOK = jarFile.canRead && (jarFile isFresher File(scriptFile)) - def jarOK = (jarFile.canRead && - (jarFile.lastModified > new File(scriptFile).lastModified)) - - if (jarOK) { - // pre-compiled jar is current - handler(jarFile.getAbsolutePath) - } else { - // The pre-compiled jar is old. Recompile the script. - jarFile.delete + def recompile() = { + jarFile.delete() compile match { case Some(compiledPath) => tryMakeJar(jarFile, compiledPath) if (jarOK) { - deleteRecursively(compiledPath) // may as well do it now - handler(jarFile.getAbsolutePath) - } else { - // jar failed; run directly from the class files - handler(compiledPath.getPath) + compiledPath.deleteRecursively() + handler(jarFile.toAbsolute.path) } - case None => false + // jar failed; run directly from the class files + else handler(compiledPath.path) + case _ => false } } - } else { - // don't use a cache jar at all--just use the class files - compile match { - case Some(compiledPath) => handler(compiledPath.getPath) - case None => false - } + + if (jarOK) handler(jarFile.toAbsolute.path) // pre-compiled jar is current + else recompile() // jar old - recompile the script. } + // don't use a cache jar at all--just use the class files + else compile map (cp => handler(cp.path)) getOrElse false } - /** Run a script after it has been compiled * * @returns true if execution succeeded, false otherwise */ - private def runCompiled(settings: GenericRunnerSettings, - compiledLocation: String, - scriptArgs: List[String]) : Boolean = { - def fileToURL(f: File): Option[URL] = - try { Some(f.toURL) } - catch { case e => Console.err.println(e); None } + private def runCompiled( + settings: GenericRunnerSettings, + compiledLocation: String, + scriptArgs: List[String]): Boolean = + { + def fileToURL(f: JFile): Option[URL] = + try Some(f.toURL) catch { case _: Exception => None } def paths(str: String, expandStar: Boolean): List[URL] = - for ( - file <- ClassPath.expandPath(str, expandStar) map (new File(_)) if file.exists; - val url = fileToURL(file); if !url.isEmpty - ) yield url.get + for { + file <- ClassPath.expandPath(str, expandStar) map (new JFile(_)) + if file.exists + url <- fileToURL(file) + } yield url val classpath = (paths(settings.bootclasspath.value, true) ::: @@ -376,64 +328,47 @@ object ScriptRunner { scriptMain(settings), scriptArgs.toArray) true - } catch { - case e: ClassNotFoundException => - Console.println(e) - false - case e: NoSuchMethodException => - Console.println(e) + } + catch { + case e @ (_: ClassNotFoundException | _: NoSuchMethodException) => + Console println e false - case e:InvocationTargetException => + case e: InvocationTargetException => e.getCause.printStackTrace false } } - /** Run a script file with the specified arguments and compilation * settings. * * @returns true if compilation and execution succeeded, false otherwise. */ - def runScript(settings: GenericRunnerSettings, + def runScript( + settings: GenericRunnerSettings, scriptFile: String, - scriptArgs: List[String]) : Boolean = { - val f = new File(scriptFile) - if (!f.isFile) { - throw new IOException("no such file: " + scriptFile) - } else { - try { - withCompiledScript(settings, scriptFile){compiledLocation => - runCompiled(settings, compiledLocation, scriptArgs) - } - } catch { - case e => throw e - } - } + scriptArgs: List[String]): Boolean = + { + if (File(scriptFile).isFile) + withCompiledScript(settings, scriptFile) { runCompiled(settings, _, scriptArgs) } + else + throw new IOException("no such file: " + scriptFile) } /** Run a command * * @returns true if compilation and execution succeeded, false otherwise. */ - def runCommand(settings: GenericRunnerSettings, - command: String, - scriptArgs: List[String]) : Boolean = { - val scriptFile = File.createTempFile("scalacmd", ".scala") - + def runCommand( + settings: GenericRunnerSettings, + command: String, + scriptArgs: List[String]) : Boolean = + { + val scriptFile = File.makeTemp("scalacmd", ".scala") // save the command to the file - { - val str = new FileWriter(scriptFile) - str.write(command) - str.close() - } + scriptFile writeAll List(command) - try { - withCompiledScript(settings, scriptFile.getPath){compiledLocation => - runCompiled(settings, compiledLocation, scriptArgs) - } - } catch { - case e => throw e - } finally scriptFile.delete() // in case there was a compilation error + try withCompiledScript(settings, scriptFile.path) { runCompiled(settings, _, scriptArgs) } + finally scriptFile.delete() // in case there was a compilation error } } diff --git a/src/compiler/scala/tools/nsc/io/AbstractFile.scala b/src/compiler/scala/tools/nsc/io/AbstractFile.scala index eaa3091eee..f7d73ed200 100644 --- a/src/compiler/scala/tools/nsc/io/AbstractFile.scala +++ b/src/compiler/scala/tools/nsc/io/AbstractFile.scala @@ -8,8 +8,9 @@ package scala.tools.nsc package io -import java.io.{File, FileOutputStream, IOException, InputStream, OutputStream} +import java.io.{ File => JFile, FileOutputStream, IOException, InputStream, OutputStream } import java.net.URL +import PartialFunction._ import scala.collection.mutable.ArrayBuffer @@ -17,21 +18,23 @@ import scala.collection.mutable.ArrayBuffer * @author Philippe Altherr * @version 1.0, 23/03/2004 */ -object AbstractFile { +object AbstractFile +{ + def isJarOrZip(f: Path) = cond(f.extension) { case Some("zip" | "jar") => true } /** Returns "getFile(new File(path))". */ - def getFile(path: String): AbstractFile = getFile(new File(path)) + def getFile(path: String): AbstractFile = getFile(Path(path)) + def getFile(path: Path): AbstractFile = getFile(path.toFile) /** * If the specified File exists and is a regular file, returns an * abstract regular file backed by it. Otherwise, returns <code>null</code>. */ def getFile(file: File): AbstractFile = - if (file.isFile() && file.exists()) new PlainFile(file) else null - + if (file.isFile) new PlainFile(file) else null /** Returns "getDirectory(new File(path))". */ - def getDirectory(path: String): AbstractFile = getDirectory(new File(path)) + def getDirectory(path: Path): AbstractFile = getDirectory(path.toFile) /** * If the specified File exists and is either a directory or a @@ -41,15 +44,10 @@ object AbstractFile { * @param file ... * @return ... */ - def getDirectory(file: File): AbstractFile = { - if (file.isDirectory() && file.exists()) return new PlainFile(file) - if (file.isFile() && file.exists()) { - val path = file.getPath() - if (path.endsWith(".jar") || path.endsWith(".zip")) - return ZipArchive.fromFile(file); - } - null - } + def getDirectory(file: File): AbstractFile = + if (file.isDirectory) new PlainFile(file) + else if (file.isFile && isJarOrZip(file)) ZipArchive fromFile file + else null /** * If the specified URL exists and is a readable zip or jar archive, @@ -60,15 +58,7 @@ object AbstractFile { * @return ... */ def getURL(url: URL): AbstractFile = - if (url ne null) { - val path = url.getPath() - if (path.endsWith(".jar") || path.endsWith(".zip")) - ZipArchive.fromURL(url) - else - null - } - else - null + Option(url) filterMap { case url: URL if isJarOrZip(url.getPath) => ZipArchive fromURL url } orNull } /** @@ -110,11 +100,12 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { def container : AbstractFile /** Returns the underlying File if any and null otherwise. */ - def file: File + def file: JFile + def sfile = File(file) // XXX /** Does this abstract file denote an existing file? */ def exists: Boolean = - if (file ne null) file.exists() + if (file ne null) file.exists else true /** Create a file on disk, if one does not exist already. */ @@ -207,11 +198,11 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { private def lookup(getFile: (AbstractFile, String, Boolean) => AbstractFile, path0: String, directory: Boolean): AbstractFile = { - val separator = File.separatorChar + val separator = JFile.separatorChar // trim trailing '/'s - val path = if (path0.charAt(path0.length - 1) == separator) path0.substring(0, path0.length - 1) else path0 + val path: String = if (path0.last == separator) path0 dropRight 1 else path0 val length = path.length() - assert(0 < length && path.lastIndexOf(separator) < length - 1, path) + assert(length > 0 && !(path.last == separator), path) var file = this var start = 0 while (true) { @@ -231,14 +222,7 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { */ def fileNamed(name: String): AbstractFile = { assert(isDirectory) - val existing = lookupName(name, false) - if (existing == null) { - val newFile = new File(file, name) - newFile.createNewFile() - new PlainFile(newFile) - } else { - existing - } + Option(lookupName(name, false)) getOrElse new PlainFile((sfile / name).createFile()) } /** @@ -247,14 +231,7 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { */ def subdirectoryNamed(name: String): AbstractFile = { assert (isDirectory) - val existing = lookupName(name, true) - if (existing == null) { - val dir = new File(file, name) - dir.mkdir() - new PlainFile(dir) - } else { - existing - } + Option(lookupName(name, true)) getOrElse new PlainFile((sfile / name).createDirectory()) } /** Returns the path of this abstract file. */ diff --git a/src/compiler/scala/tools/nsc/io/Directory.scala b/src/compiler/scala/tools/nsc/io/Directory.scala new file mode 100644 index 0000000000..dd48ee2866 --- /dev/null +++ b/src/compiler/scala/tools/nsc/io/Directory.scala @@ -0,0 +1,73 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2009, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools.nsc +package io + +import java.io.{ File => JFile } +import collection.Traversable + +object Directory +{ + def apply(path: Path) = path.toDirectory + + // Like File.makeTemp but creates a directory instead + def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null): Directory = { + val path = File.makeTemp(prefix, suffix, dir) + path.delete() + path.createDirectory() + } +} +import Path._ + +/** An abstraction for directories. + * + * @author Paul Phillips + * @since 2.8 + */ +class Directory(jfile: JFile) extends Path(jfile) +{ + override def toDirectory: Directory = this + override def toFile: File = new File(jfile) + override def isValid = jfile.isDirectory() || !jfile.exists() + + /** An iterator over the contents of this directory. + */ + def list: Iterator[Path] = + jfile.listFiles match { + case null => Iterator.empty + case xs => xs.iterator map Path.apply + } + + def dirs: Iterator[Directory] = list filterMap { case x: Directory => x } + def files: Iterator[File] = list filterMap { case x: File => x } + + def deepList(depth: Int = 1): Iterator[Path] = + if (depth == 0) Iterator.empty + else list ++ (dirs flatMap (_ deepList (depth - 1))) + + /** An iterator over the directories underneath this directory, + * to the (optionally) given depth. + */ + def subdirs(depth: Int = 1): Iterator[Directory] = + deepList(depth) filterMap { case x: Directory => x } + + /** Deletes the directory recursively. Returns false on failure. + * Use with caution! + */ + def deleteRecursively(): Boolean = deleteRecursively(jfile) + private def deleteRecursively(f: JFile): Boolean = { + if (f.isDirectory) f.listFiles match { + case null => + case xs => xs foreach deleteRecursively + } + f.delete() + } + + override def toString() = "Directory(%s)".format(path) +} diff --git a/src/compiler/scala/tools/nsc/io/File.scala b/src/compiler/scala/tools/nsc/io/File.scala new file mode 100644 index 0000000000..294139ba44 --- /dev/null +++ b/src/compiler/scala/tools/nsc/io/File.scala @@ -0,0 +1,122 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2009, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id$ + +package scala.tools.nsc +package io + +import java.io.{ + FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter, + BufferedInputStream, BufferedOutputStream, IOException, File => JFile } +import java.nio.channels.FileChannel +import collection.Traversable +import scala.io.Codec + +object File +{ + def pathSeparator = JFile.pathSeparator + + def apply(path: Path)(implicit codec: Codec = null) = + if (codec != null) new File(path.jfile)(codec) + else path.toFile + + // Create a temporary file + def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null) = + apply(JFile.createTempFile(prefix, suffix, dir)) + + import java.nio.channels.Channel + type Closeable = { def close(): Unit } + def closeQuietly(target: Closeable) { + try target.close() catch { case e: IOException => } + } +} +import File._ +import Path._ + +/** An abstraction for files. For character data, a Codec + * can be supplied at either creation time or when a method + * involving character data is called (with the latter taking + * precdence if supplied.) If neither is available, the value + * of scala.io.Codec.default is used. + * + * @author Paul Phillips + * @since 2.8 + */ +class File(jfile: JFile)(implicit val creationCodec: Codec = null) +extends Path(jfile) +with Streamable.Chars +{ + def withCodec(codec: Codec): File = new File(jfile)(codec) + override def toDirectory: Directory = new Directory(jfile) + override def toFile: File = this + + override def isValid = jfile.isFile() || !jfile.exists() + override def length = super[Path].length + + /** Obtains an InputStream. */ + def inputStream() = new FileInputStream(jfile) + + /** Obtains a OutputStream. */ + def outputStream(append: Boolean = false) = new FileOutputStream(jfile, append) + def bufferedOutput(append: Boolean = false) = new BufferedOutputStream(outputStream(append)) + + /** Obtains an OutputStreamWriter wrapped around a FileOutputStream. + * This should behave like a less broken version of java.io.FileWriter, + * in that unlike the java version you can specify the encoding. + */ + def writer(append: Boolean = false, codec: Codec = getCodec()) = + new OutputStreamWriter(outputStream(append), codec.charSet) + + /** Wraps a BufferedWriter around the result of writer(). + */ + def bufferedWriter(append: Boolean = false, codec: Codec = getCodec()) = + new BufferedWriter(writer(append, codec)) + + /** Writes all the Strings in the given iterator to the file. */ + def writeAll(xs: Traversable[String], append: Boolean = false, codec: Codec = getCodec()): Unit = { + val out = bufferedWriter(append, codec) + try xs foreach (out write _) + finally out close + } + + def copyFile(destPath: Path, preserveFileDate: Boolean = false) = { + val FIFTY_MB = 1024 * 1024 * 50 + val dest = destPath.toFile + if (!isValid) fail("Source %s is not a valid file." format name) + if (this.normalize == dest.normalize) fail("Source and destination are the same.") + if (!dest.parent.map(_.exists).getOrElse(false)) fail("Destination cannot be created.") + if (dest.exists && !dest.canWrite) fail("Destination exists but is not writable.") + if (dest.isDirectory) fail("Destination exists but is a directory.") + + lazy val in_s = inputStream() + lazy val out_s = dest.outputStream() + lazy val in = in_s.getChannel() + lazy val out = out_s.getChannel() + + try { + val size = in.size() + var pos, count = 0L + while (pos < size) { + count = (size - pos) min FIFTY_MB + pos += out.transferFrom(in, pos, count) + } + } + finally List[Closeable](out, out_s, in, in_s) foreach closeQuietly + + if (this.length != dest.length) + fail("Failed to completely copy %s to %s".format(name, dest.name)) + + if (preserveFileDate) + dest.lastModified = this.lastModified + + () + } + + override def toString() = "File(%s)".format(path) +} diff --git a/src/compiler/scala/tools/nsc/io/FileOperationException.scala b/src/compiler/scala/tools/nsc/io/FileOperationException.scala new file mode 100644 index 0000000000..f4983cd156 --- /dev/null +++ b/src/compiler/scala/tools/nsc/io/FileOperationException.scala @@ -0,0 +1,14 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2009, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id$ + +package scala.tools.nsc +package io + +case class FileOperationException(msg: String) extends RuntimeException(msg) diff --git a/src/compiler/scala/tools/nsc/io/Path.scala b/src/compiler/scala/tools/nsc/io/Path.scala new file mode 100644 index 0000000000..40b6a3a4ad --- /dev/null +++ b/src/compiler/scala/tools/nsc/io/Path.scala @@ -0,0 +1,169 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2009 LAMP/EPFL + */ + +package scala.tools.nsc +package io + +import java.io.{ + FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter, + BufferedInputStream, BufferedOutputStream, File => JFile } +import java.net.{ URI, URL } +import collection.{ Sequence, Traversable } +import collection.immutable.{ StringVector => SV } +import PartialFunction._ +import scala.util.Random.nextASCIIString + +/** An abstraction for filesystem paths. The differences between + * Path, File, and Directory are primarily to communicate intent. + * Since the filesystem can change at any time, there is no way to + * reliably associate Files only with files and so on. Any Path + * can be converted to a File or Directory (and thus gain access to + * the additional entity specific methods) by calling toFile or + * toDirectory, which has no effect on the filesystem. + * + * Also available are createFile and createDirectory, which attempt + * to create the path in question. + * + * @author Paul Phillips + * @since 2.8 + */ + +object Path +{ + // not certain these won't be problematic, but looks good so far + implicit def string2path(s: String): Path = apply(s) + implicit def jfile2path(jfile: JFile): Path = apply(jfile) + + // java 7 style, we don't use it yet + // object AccessMode extends Enumeration("AccessMode") { + // val EXECUTE, READ, WRITE = Value + // } + // def checkAccess(modes: AccessMode*): Boolean = { + // modes foreach { + // case EXECUTE => throw new Exception("Unsupported") // can't check in java 5 + // case READ => if (!jfile.canRead()) return false + // case WRITE => if (!jfile.canWrite()) return false + // } + // true + // } + + def roots: List[Path] = JFile.listRoots().toList map Path.apply + + def apply(path: String): Path = apply(new JFile(path)) + def apply(jfile: JFile): Path = + if (jfile.isFile) new File(jfile) + else if (jfile.isDirectory) new Directory(jfile) + else new Path(jfile) + + private[io] def randomPrefix = nextASCIIString(6) + private[io] def fail(msg: String) = throw FileOperationException(msg) +} +import Path._ + +/** The Path constructor is private so we can enforce some + * semantics regarding how a Path might relate to the world. + */ +class Path private[io] (val jfile: JFile) +{ + val separator = JFile.separatorChar + + // Validation: this verifies that the type of this object and the + // contents of the filesystem are in agreement. All objects are + // valid except File objects whose path points to a directory and + // Directory objects whose path points to a file. + def isValid: Boolean = true + + // conversions + def toFile: File = new File(jfile) + def toDirectory: Directory = new Directory(jfile) + def toAbsolute: Path = if (isAbsolute) this else Path(jfile.getAbsolutePath()) + def toURI: URI = jfile.toURI() + def toURL: URL = toURI.toURL() + + /** Creates a new Path with the specified path appended. Assumes + * the type of the new component implies the type of the result. + */ + def /(child: Path): Path = new Path(new JFile(jfile, child.path)) + def /(child: Directory): Directory = /(child: Path).toDirectory + def /(child: File): File = /(child: Path).toFile + + // identity + def name: String = jfile.getName() + def path: String = jfile.getPath() + def normalize: Path = Path(jfile.getCanonicalPath()) + // todo - + // def resolve(other: Path): Path + // def relativize(other: Path): Path + + // derived from identity + def root: Option[Path] = roots find (this startsWith _) + def segments: List[String] = (path split separator).toList filterNot (_.isEmpty) + def parent: Option[Path] = Option(jfile.getParent()) map Path.apply + def parents: List[Path] = parent match { + case None => Nil + case Some(p) => p :: p.parents + } + // if name ends with an extension (e.g. "foo.jpg") returns the extension ("jpg") + def extension: Option[String] = + condOpt(SV.lastIndexWhere(name, _ == '.')) { + case idx if idx != -1 => SV.drop(name, idx + 1) + } + // Alternative approach: + // (Option fromReturnValue SV.lastIndexWhere(name, _ == '.') map (x => SV.drop(name, x + 1)) + + // Boolean tests + def canRead = jfile.canRead() + def canWrite = jfile.canWrite() + def exists = jfile.exists() + def notExists = try !jfile.exists() catch { case ex: SecurityException => false } + + def isFile = jfile.isFile() + def isDirectory = jfile.isDirectory() + def isAbsolute = jfile.isAbsolute() + def isHidden = jfile.isHidden() + def isSymlink = parent.isDefined && { + val x = parent.get / name + x.normalize != x.toAbsolute + } + + // Information + def lastModified = jfile.lastModified() + def lastModified_=(time: Long) = jfile setLastModified time // should use setXXX function? + def length = jfile.length() + + // Boolean path comparisons + def endsWith(other: Path) = segments endsWith other.segments + def startsWith(other: Path) = segments startsWith other.segments + def isSame(other: Path) = toAbsolute == other.toAbsolute + def isFresher(other: Path) = lastModified > other.lastModified + + // creations + def createDirectory(force: Boolean = true, failIfExists: Boolean = false): Directory = { + val res = if (force) jfile.mkdirs() else jfile.mkdir() + if (!res && failIfExists && exists) fail("Directory '%s' already exists." format name) + else if (isDirectory) toDirectory + else new Directory(jfile) + } + def createFile(failIfExists: Boolean = false): File = { + val res = jfile.createNewFile() + if (!res && failIfExists && exists) fail("File '%s' already exists." format name) + else if (isFile) toFile + else new File(jfile) + } + + // deletions + def delete() = jfile.delete() + def deleteIfExists() = if (jfile.exists()) delete() else false + + // todo + // def copyTo(target: Path, options ...): Boolean + // def moveTo(target: Path, options ...): Boolean + + override def toString() = "Path(%s)".format(path) + override def equals(other: Any) = other match { + case x: Path => path == x.path + case _ => false + } + override def hashCode() = path.hashCode() +} diff --git a/src/compiler/scala/tools/nsc/io/PlainFile.scala b/src/compiler/scala/tools/nsc/io/PlainFile.scala index 0394a16a93..926f5ee042 100644 --- a/src/compiler/scala/tools/nsc/io/PlainFile.scala +++ b/src/compiler/scala/tools/nsc/io/PlainFile.scala @@ -8,7 +8,8 @@ package scala.tools.nsc package io -import java.io.{File, FileInputStream, FileOutputStream, IOException} +import java.io.{ File => JFile, FileInputStream, FileOutputStream, IOException } +import PartialFunction._ object PlainFile { @@ -16,56 +17,46 @@ object PlainFile * If the specified File exists, returns an abstract file backed * by it. Otherwise, returns null. */ - def fromFile(file: File): PlainFile = - if (file.exists()) new PlainFile(file) else null - - def fromPath(path: String): PlainFile = fromFile(new File(path)) + def fromPath(file: Path): PlainFile = + if (file.exists) new PlainFile(file) else null } /** This class implements an abstract file backed by a File. */ -class PlainFile(val file: File) extends AbstractFile { - private val fpath = try { file.getCanonicalPath } - catch { case _: IOException => file.getAbsolutePath } +class PlainFile(val givenPath: Path) extends AbstractFile { + assert(path ne null) - assert(file ne null) -// assert(file.exists(), "non-existent file: " + file) + val file = givenPath.jfile + private val fpath = try givenPath.normalize catch { case _: IOException => givenPath.toAbsolute } /** Returns the name of this abstract file. */ - def name = file.getName() + def name = givenPath.name /** Returns the path of this abstract file. */ - def path = file.getPath() + def path = givenPath.path /** The absolute file. */ - def absolute = new PlainFile(file.getCanonicalFile()) - - override def container : AbstractFile = new PlainFile(file.getParentFile) + def absolute = new PlainFile(givenPath.normalize) - override def input = new FileInputStream(file) - override def output = new FileOutputStream(file) - - override def sizeOption = Some(file.length.toInt) + override def container: AbstractFile = new PlainFile(givenPath.parent.get) + override def input = givenPath.toFile.inputStream() + override def output = givenPath.toFile.outputStream() + override def sizeOption = Some(givenPath.length.toInt) override def hashCode(): Int = fpath.hashCode - override def equals(that: Any): Boolean = - that.isInstanceOf[PlainFile] && - fpath.equals(that.asInstanceOf[PlainFile].fpath) + cond(that) { case other: PlainFile => fpath == other.fpath } /** Is this abstract file a directory? */ - def isDirectory: Boolean = file.isDirectory() + def isDirectory: Boolean = givenPath.isDirectory /** Returns the time that this abstract file was last modified. */ - def lastModified: Long = file.lastModified() + def lastModified: Long = givenPath.lastModified /** Returns all abstract subfiles of this abstract directory. */ def iterator: Iterator[AbstractFile] = { - assert(isDirectory, "not a directory '" + this + "'") - val names: Array[String] = file.list() - if ((names eq null) || names.length == 0) Iterator.empty - else names.iterator.map { name: String => new File(file, name) } - .filter(_.exists).map(file => new PlainFile(file)) + assert(isDirectory, "not a directory '%s'" format this) + givenPath.toDirectory.list filter (_.exists) map (new PlainFile(_)) } /** @@ -79,34 +70,22 @@ class PlainFile(val file: File) extends AbstractFile { * @return ... */ def lookupName(name: String, directory: Boolean): AbstractFile = { - //assert(isDirectory, "not a directory '" + this + "'") - val child = new File(file, name) - if (!child.exists() || (directory != child.isDirectory) || - directory == child.isFile()) null - else new PlainFile(child) + val child = givenPath / name + if ((child.isDirectory && directory) || (child.isFile && !directory)) new PlainFile(child) + else null } /** Does this abstract file denote an existing file? */ - def create { - if (!exists) - file.createNewFile() - } + def create: Unit = if (!exists) givenPath.createFile() /** Delete the underlying file or directory (recursively). */ - def delete { - if (file.isFile) file.delete - else if (file.isDirectory) { - iterator.foreach(_.delete) - file.delete - } - } + def delete: Unit = + if (givenPath.isFile) givenPath.delete() + else if (givenPath.isDirectory) givenPath.toDirectory.deleteRecursively() /** Returns a plain file with the given name. It does not * check that it exists. */ - def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = { - val f = new File(file, name) - new PlainFile(f) - } - + def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = + new PlainFile(givenPath / name) } diff --git a/src/compiler/scala/tools/nsc/io/Process.scala b/src/compiler/scala/tools/nsc/io/Process.scala new file mode 100644 index 0000000000..6a1029fa02 --- /dev/null +++ b/src/compiler/scala/tools/nsc/io/Process.scala @@ -0,0 +1,159 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2009 LAMP/EPFL + */ + +package scala.tools.nsc +package io + +import annotation.experimental +import concurrent.ThreadRunner +import scala.util.Properties.{ isWin, isMac } +import scala.util.control.Exception.catching +import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder } +import java.io.{ IOException, InputStream, OutputStream, BufferedReader, InputStreamReader, PrintWriter, File => JFile } +import java.util.concurrent.LinkedBlockingQueue + +/** The <code>Process</code> object contains convenience functions + * for running external processes. + * + * An example usage: + * <pre> + * io.Process("ls", cwd = io.File("/")) foreach println + * </pre> + * + * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4109888 + * for a dated list of the many obstacles to a clean interface. + * + * This is not finished!! Do not rely upon it yet. + * + * TODO - remove requirement that process complete before we + * can get an iterator. + * + * @author Paul Phillips + * @since 2.8 + */ + +@experimental +object Process +{ + lazy val javaVmArguments = java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments() + lazy val runtime = Runtime.getRuntime() + + @experimental + private[Process] class ProcessBuilder(val pb: JProcessBuilder) + { + def this(cmd: String*) = this(new JProcessBuilder(cmd.toArray: _*)) + def start() = new Process(() => pb.start()) + + def withOnlyEnv(env: Map[String, String]): this.type = { + pb.environment.clear() + withEnv(env) + } + + def withEnv(env: Map[String,String]): this.type = { + if (env != null) { + val jmap = pb.environment() + for ((k, v) <- env) jmap.put(k, v) + } + this + } + + def withCwd(cwd: File): this.type = { + if (cwd != null) + pb directory cwd.jfile + + this + } + def withRedirectedErrorStream(merged: Boolean): this.type = { + pb redirectErrorStream merged + this + } + + override def toString() = "ProcessBuilder(%s)" format pb.command() + } + + // This can be fleshed out if more variations come up + private val shell: String => Array[String] = + if (isWin) Array("cmd.exe", "/C", _) + else Array("sh", "-c", _) + + /** Executes the given command line in a shell. + * + * @param command the command line + * @return a Process object + */ + def apply( + command: String, + env: Map[String, String] = null, + cwd: File = null, + redirect: Boolean = false + ): Process = + exec(shell(command), env, cwd) + + /** Executes the given command line. + * + * @param command the command line + * @return a Process object + */ + def exec( + command: Seq[String], + env: Map[String, String] = null, + cwd: File = null, + redirect: Boolean = false + ): Process = + new ProcessBuilder(command: _*) withEnv env withCwd cwd start +} +import Process._ + +@experimental +class Process(processCreator: () => JProcess) extends Iterable[String] +{ + lazy val process = processCreator() + + def exitValue(): Option[Int] = + catching(classOf[IllegalThreadStateException]) opt process.exitValue() + + def waitFor() = process.waitFor() + def destroy() = process.destroy() + def rerun() = new Process(processCreator) + + def stdout = iterator + def iterator = _out.iterator + def stderr = _err.iterator + lazy val stdin = new PrintWriter(_in, true) + + class StreamedConsumer(in: InputStream) extends Thread with Iterable[String] { + private val queue = new LinkedBlockingQueue[String] + private val reader = new BufferedReader(new InputStreamReader(in)) + + def iterator = { + join() // make sure this thread is complete + new Iterator[String] { + val it = queue.iterator() + def hasNext = it.hasNext + def next = it.next + } + } + override def run() { + reader.readLine match { + case null => + case x => + queue put x + run() + } + } + } + + private val _err = createConsumer(process.getErrorStream) + private val _out = createConsumer(process.getInputStream) + private val _in = process.getOutputStream() + + private def createConsumer(in: InputStream) = { + val t = new StreamedConsumer(in) + t.start() + t + } + + override def toString() = "Process(%s)" format process.toString() +} + diff --git a/src/compiler/scala/tools/nsc/io/SourceReader.scala b/src/compiler/scala/tools/nsc/io/SourceReader.scala index 21b4305b6b..f2b3373619 100644 --- a/src/compiler/scala/tools/nsc/io/SourceReader.scala +++ b/src/compiler/scala/tools/nsc/io/SourceReader.scala @@ -8,7 +8,7 @@ package scala.tools.nsc package io -import java.io.{File, FileInputStream, InputStream, IOException} +import java.io.{ FileInputStream, InputStream, IOException, File => JFile } import java.nio.{ByteBuffer, CharBuffer} import java.nio.channels.{FileChannel, ReadableByteChannel, Channels} import java.nio.charset.{CharsetDecoder, CoderResult} @@ -38,10 +38,10 @@ class SourceReader(decoder: CharsetDecoder, reporter: Reporter) { // Public Methods /** Reads the file with the specified name. */ - def read(filename: String): Array[Char]= read(new File(filename)) + def read(filename: String): Array[Char]= read(new JFile(filename)) /** Reads the specified file. */ - def read(file: File): Array[Char] = { + def read(file: JFile): Array[Char] = { val c = new FileInputStream(file).getChannel try { read(c) diff --git a/src/compiler/scala/tools/nsc/io/Streamable.scala b/src/compiler/scala/tools/nsc/io/Streamable.scala new file mode 100644 index 0000000000..0ff1c48ce5 --- /dev/null +++ b/src/compiler/scala/tools/nsc/io/Streamable.scala @@ -0,0 +1,108 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2009 LAMP/EPFL + */ + +package scala.tools.nsc +package io + +import java.net.{ URI, URL } +import java.io.{ BufferedInputStream, InputStream, PrintStream, File => JFile } +import java.io.{ BufferedReader, InputStreamReader } +import scala.io.{ Codec, Source } + +import collection.mutable.ArrayBuffer +import Path.fail + +/** Traits for objects which can be represented as Streams. + * + * @author Paul Phillips + * @since 2.8 + */ + +object Streamable +{ + /** Traits which can be viewed as a sequence of bytes. Source types + * which know their length should override def length: Long for more + * efficient method implementations. + */ + trait Bytes { + def inputStream(): InputStream + def length: Long = -1 + + def bufferedInput() = new BufferedInputStream(inputStream()) + def bytes(): Iterator[Byte] = bytesAsInts() map (_.toByte) + def bytesAsInts(): Iterator[Int] = { + val in = bufferedInput() + Iterator continually in.read() takeWhile (_ != -1) + } + + /** This method aspires to be the fastest way to read + * a stream of known length into memory. + */ + def toByteArray(): Array[Byte] = { + // if we don't know the length, fall back on relative inefficiency + if (length == -1L) + return (new ArrayBuffer[Byte]() ++ bytes()).toArray + + val arr = new Array[Byte](length.toInt) + val len = arr.length + lazy val in = bufferedInput() + var offset = 0 + + def loop() { + if (offset < len) { + val read = in.read(arr, offset, len - offset) + if (read >= 0) { + offset += read + loop() + } + } + } + try loop() + finally in.close() + + if (offset == arr.length) arr + else fail("Could not read entire source (%d of %d bytes)".format(offset, len)) + } + } + + /** For objects which can be viewed as Chars. The abstract creationCodec + * can safely be defined as null and will subsequently be ignored. + */ + trait Chars extends Bytes { + def creationCodec: Codec + private def failNoCodec() = fail("This method requires a Codec to be chosen explicitly.") + + /** The general algorithm for any call to a method involving byte<->char + * transformations is: if a codec is supplied (explicitly or implicitly), + * use that; otherwise if a codec was defined when the object was created, + * use that; otherwise, use Codec.default. + * + * Note that getCodec takes a codec argument rather than having methods + * always default to getCodec() and use the argument otherwise, so that + * method implementations can, where desired, identify the case where no + * codec was ever explicitly supplied. If allowDefault = false, an + * exception will be thrown rather than falling back on Codec.default. + */ + def getCodec(givenCodec: Codec = null, allowDefault: Boolean = true) = + if (givenCodec != null) givenCodec + else if (creationCodec != null) creationCodec + else if (allowDefault) Codec.default + else failNoCodec() + + def chars(codec: Codec = getCodec()): Source = (Source fromInputStream inputStream())(codec) + def lines(codec: Codec = getCodec()): Iterator[String] = chars(codec).getLines() + + /** Obtains an InputStreamReader wrapped around a FileInputStream. + */ + def reader(codec: Codec = getCodec()) = new InputStreamReader(inputStream, codec.charSet) + + /** Wraps a BufferedReader around the result of reader(). + */ + def bufferedReader(codec: Codec = getCodec()) = new BufferedReader(reader(codec)) + + /** Convenience function to import entire file into a String. + */ + def slurp(codec: Codec = getCodec()) = chars(codec).mkString + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala b/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala index 16381bbe6e..0d394fdddd 100644 --- a/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala +++ b/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala @@ -1,10 +1,11 @@ /* NSC -- new Scala compiler * Copyright 2005-2009 LAMP/EPFL */ -// $Id$ + package scala.tools.nsc package io -import scala.collection.{mutable=>mut} + +import scala.collection.mutable /** * An in-memory directory. @@ -24,9 +25,7 @@ extends AbstractFile { def container = maybeContainer.get def isDirectory = true var lastModified: Long = System.currentTimeMillis - private def updateLastModified { - lastModified = System.currentTimeMillis - } + override def file = null override def input = error("directories cannot be read") override def output = error("directories cannot be written") @@ -47,44 +46,28 @@ extends AbstractFile { def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = throw new UnsupportedOperationException() - private val files = mut.Map.empty[String, AbstractFile] + private val files = mutable.Map.empty[String, AbstractFile] // the toList is so that the directory may continue to be // modified while its elements are iterated def iterator = files.valuesIterator.toList.iterator - override def lookupName(name: String, directory: Boolean): AbstractFile = { - files.get(name) match { - case None => null - case Some(file) => - if (file.isDirectory == directory) - file - else - null - } - } + override def lookupName(name: String, directory: Boolean): AbstractFile = + files get name filter (_.isDirectory == directory) orNull - override def fileNamed(name: String): AbstractFile = { - val existing = lookupName(name, false) - if (existing == null) { + override def fileNamed(name: String): AbstractFile = + Option(lookupName(name, false)) getOrElse { val newFile = new VirtualFile(name, path+'/'+name) files(name) = newFile newFile - } else { - existing } - } - override def subdirectoryNamed(name: String): AbstractFile = { - val existing = lookupName(name, true) - if (existing == null) { + override def subdirectoryNamed(name: String): AbstractFile = + Option(lookupName(name, true)) getOrElse { val dir = new VirtualDirectory(name, Some(this)) files(name) = dir dir - } else { - existing } - } def clear() { files.clear(); diff --git a/src/compiler/scala/tools/nsc/io/VirtualFile.scala b/src/compiler/scala/tools/nsc/io/VirtualFile.scala index 14d081991e..90769d7086 100644 --- a/src/compiler/scala/tools/nsc/io/VirtualFile.scala +++ b/src/compiler/scala/tools/nsc/io/VirtualFile.scala @@ -8,16 +8,16 @@ package scala.tools.nsc package io -import java.io.{ByteArrayInputStream, ByteArrayOutputStream, - File, InputStream, OutputStream} +import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, InputStream, OutputStream, File => JFile } +import PartialFunction._ /** This class implements an in-memory file. * * @author Philippe Altherr * @version 1.0, 23/03/2004 */ -class VirtualFile(val name: String, _path: String) extends AbstractFile { - +class VirtualFile(val name: String, _path: String) extends AbstractFile +{ assert((name ne null) && (path ne null), name + " - " + path) //######################################################################## @@ -33,11 +33,7 @@ class VirtualFile(val name: String, _path: String) extends AbstractFile { def this(name: String) = this(name, name) override def hashCode = name.hashCode - override def equals(that : Any) = that match { - case that : VirtualFile => name == that.name - case _ => false - } - + override def equals(that: Any) = cond(that) { case x: VirtualFile => x.name == name } //######################################################################## // Private data @@ -51,7 +47,7 @@ class VirtualFile(val name: String, _path: String) extends AbstractFile { def absolute = this /** Returns null. */ - final def file: File = null + final def file: JFile = null override def sizeOption: Option[Int] = Some(content.size) diff --git a/src/compiler/scala/tools/nsc/io/ZipArchive.scala b/src/compiler/scala/tools/nsc/io/ZipArchive.scala index 9d322af889..a3c3c85a15 100644 --- a/src/compiler/scala/tools/nsc/io/ZipArchive.scala +++ b/src/compiler/scala/tools/nsc/io/ZipArchive.scala @@ -8,28 +8,24 @@ package scala.tools.nsc package io -import java.io.{File, IOException, InputStream} import java.net.URL import java.util.Enumeration -import java.util.zip.{ZipEntry, ZipFile, ZipInputStream} +import java.io.{ File => JFile, IOException, InputStream, BufferedInputStream, ByteArrayInputStream } +import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream } +import PartialFunction._ -import scala.collection.mutable.{Map, HashMap} +import scala.collection.Traversable +import scala.collection.mutable.{ Map, HashMap } +import scala.collection.immutable.{ StringVector => SV } +import scala.collection.JavaConversions.asIterator /** * @author Philippe Altherr * @version 1.0, 23/03/2004 */ -object ZipArchive { - - //######################################################################## - - /** - * ... - * - * @param path ... - * @return ... - */ - def fromPath(path: String): AbstractFile = fromFile(new File(path)) +object ZipArchive +{ + def fromPath(path: Path): ZipArchive = fromFile(path.toFile) /** * If the specified file <code>file</code> exists and is a readable @@ -39,56 +35,115 @@ object ZipArchive { * @param file ... * @return ... */ - def fromFile(file: File): AbstractFile = - try { new ZipArchive(file, new ZipFile(file)) } + def fromFile(file: File): ZipArchive = + try new ZipArchive(file, new ZipFile(file.jfile)) catch { case _: IOException => null } /** * Returns an abstract directory backed by the specified archive. - * - * @param archive ... - * @return ... */ - def fromArchive(archive: ZipFile): AbstractFile = - new ZipArchive(new File(archive.getName()), archive) + def fromArchive(archive: ZipFile): ZipArchive = + new ZipArchive(File(archive.getName()), archive) /** * Returns an abstract directory backed by the specified archive. - * - * @param url ... - * @return ... */ - def fromURL(url: URL): AbstractFile = - new URLZipArchive(url) + def fromURL(url: URL): AbstractFile = new URLZipArchive(url) + + private[io] class ZipEntryTraversableClass(in: InputStream) extends Traversable[ZipEntry] { + val zis = new ZipInputStream(in) + + def foreach[U](f: ZipEntry => U) = { + def loop(x: ZipEntry): Unit = if (x != null) { + f(x) + zis.closeEntry() + loop(zis.getNextEntry()) + } + loop(zis.getNextEntry()) + } + } } -/** - * This class implements an abstract directory backed by a zip - * archive. We let the encoding be <code>null</code>, because we behave like - * a directory. - * - * @author Philippe Altherr - * @version 1.0, 23/03/2004 +/** This abstraction aims to factor out the common code between + * ZipArchive (backed by a zip file) and URLZipArchive (backed + * by an InputStream.) */ -final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) { +private[io] trait ZipContainer extends AbstractFile +{ + /** Abstract types */ + type SourceType // InputStream or AbstractFile + type CreationType // InputStream or ZipFile + type ZipTrav = Traversable[ZipEntry] { def zis: ZipInputStream } + + /** Abstract values */ + protected val creationSource: CreationType + protected val root: DirEntryInterface + protected def DirEntryConstructor: (AbstractFile, String, String) => DirEntryInterface + protected def FileEntryConstructor: (SourceType, String, String, ZipEntry) => FileEntryInterface + protected def ZipTravConstructor: CreationType => ZipTrav + + protected[io] trait EntryInterface extends VirtualFile { + def name: String + def path: String + } - assert(archive ne null) - //######################################################################## - // Private Fields + protected[io] trait DirEntryInterface extends EntryInterface { + def source: SourceType + val entries: Map[String, EntryInterface] = new HashMap() + var entry: ZipEntry = _ - /** The root directory or null if not yet initialized */ - private var root: DirEntry = _ + override def input = throw new Error("cannot read directories") + override def lastModified: Long = + if (entry ne null) entry.getTime() else super.lastModified - //######################################################################## - // Public Methods + override def isDirectory = true + override def iterator: Iterator[AbstractFile] = entries.valuesIterator + override def lookupName(name: String, directory: Boolean): AbstractFile = { + def slashName = if (directory) name + "/" else name + entries.getOrElse(slashName, null) + } + } - /** Returns true. */ - override def isDirectory = true + protected[io] trait FileEntryInterface extends EntryInterface { + def entry: ZipEntry - /** Returns all abstract subfiles of this abstract directory. */ - override def iterator: Iterator[AbstractFile] = { - if (root eq null) load() - root.iterator + override def lastModified: Long = entry.getTime() + override def sizeOption = Some(entry.getSize().toInt) + } + + class ZipRootCreator(f: ZipRootCreator => SourceType) { + val root = DirEntryConstructor(ZipContainer.this, "<root>", "/") + + // Map from paths to DirEntries + val dirs = HashMap[String, DirEntryInterface]("/" -> root) + val traverser = ZipTravConstructor(creationSource) + private[this] var _parent: DirEntryInterface = _ + def parent = _parent + + def addEntry(entry: ZipEntry) { + val path = entry.getName + if (entry.isDirectory) { + val dir: DirEntryInterface = getDir(dirs, path) + if (dir.entry == null) dir.entry = entry + } + else { + val (home, name) = splitPath(path) + _parent = getDir(dirs, home) + _parent.entries(name) = FileEntryConstructor(f(this), name, path, entry) + } + } + + def apply() = { + traverser foreach addEntry + root + } + } + + protected def splitPath(path: String): (String, String) = { + (path lastIndexOf '/') match { + case -1 => ("/", path) + case idx => SV.splitAt(path, idx + 1) + } } /** @@ -97,10 +152,8 @@ final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) * argument "directory" tells whether to look for a directory or * or a regular file. */ - override def lookupName(name: String, directory: Boolean): AbstractFile = { - if (root eq null) load() + override def lookupName(name: String, directory: Boolean): AbstractFile = root.lookupName(name, directory) - } /** Returns an abstract file with the given name. It does not * check that it exists. @@ -108,114 +161,90 @@ final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) override def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = throw new UnsupportedOperationException() - //######################################################################## - // Private Methods - - /** Loads the archive and creates the root directory. */ - private def load() { - this.root = new DirEntry(this, "<root>", "/") - // A path to DirEntry map - val dirs: Map[String, DirEntry] = new HashMap() - dirs.update("/", root) - val entries = archive.entries() - while (entries.hasMoreElements()) { - val entry = entries.nextElement().asInstanceOf[ZipEntry] - val path = entry.getName() - assert(entry.isDirectory() == path.endsWith("/"), - this.toString() + " - " + path); - if (entry.isDirectory()) { - val dir: DirEntry = getDir(dirs, path) - // this assertion causes an unnecessary bomb if a directory is twice listed in the jar - // assert(dir.entry eq null, this.toString() + " - " + path) - if (dir.entry eq null) dir.entry = entry - } else { - val index = path.lastIndexOf('/') - val name = if (index < 0) path else path.substring(index + 1) - val home = if (index < 0) "/" else path.substring(0, index + 1) - val parent: DirEntry = getDir(dirs, home) - // OLD: assert(!parent.entries.contains(path)) - // MAYBE: assert(!parent.entries.contains(name)) - //if (parent.entries.contains(name)) - // Console.println("XXX: " + this.toString() + " - " + home + "/" + name) - parent.entries.update(name, new FileEntry(parent, name, path, entry)) - } - } - } + /** Returns all abstract subfiles of this abstract directory. */ + override def iterator: Iterator[AbstractFile] = root.iterator /** - * Lookups the specified table for a DirEntry with the specified - * path. If successful, returns the found DirEntry. Otherwise - * creates a new DirEntry, enters it into the table and in the - * table of its parent ZipDir and returns it. + * Looks up the path in the given map and returns if found. + * If not present, creates a new DirEntry, adds to both given + * map and parent.entries, and returns it. */ - private def getDir(dirs: Map[String,DirEntry], path: String): DirEntry = - dirs.get(path) match { - case Some(dir) => dir - case None => - val index = path.lastIndexOf('/', path.length() - 2); - val name = if (index < 0) path else path.substring(index + 1); - val home = if (index < 0) "/" else path.substring(0, index + 1); - val parent: DirEntry = getDir(dirs, home); - val dir = new DirEntry(parent, name.substring(0, name.length() - 1), path); - parent.entries.update(name, dir); - dirs.update(path, dir); - dir - } - - //######################################################################## - // Private Class - Entry - - /** Superclass of archive entries */ - abstract class Entry(override val container : AbstractFile, name: String, path: String) - extends VirtualFile(name, path) { - final override def path = ZipArchive.this.toString() + "(" + pathInArchive + ")" - final def getArchive = ZipArchive.this.archive - def pathInArchive = super.path - override def hashCode = super.hashCode + container.hashCode - override def equals(that : Any) = super.equals(that) && (that match { - case entry : Entry => container == entry.container - case _ => false + protected def getDir(dirs: Map[String, DirEntryInterface], path: String): DirEntryInterface = + dirs.getOrElseUpdate(path, { + val (home, name) = splitPath(SV init path) + val parent = getDir(dirs, home) + val dir = DirEntryConstructor(parent, name, path) + parent.entries(name + path.last) = dir + dir }) - } - //######################################################################## - // Private Class - DirEntry + override def isDirectory = true +} - /** A directory archive entry */ - private final class DirEntry(container : AbstractFile, name: String, path: String) - extends Entry(container, name, path) +/** + * This class implements an abstract directory backed by a zip + * archive. We let the encoding be <code>null</code>, because we behave like + * a directory. + * + * @author Philippe Altherr + * @version 1.0, 23/03/2004 + */ +final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) with ZipContainer +{ + self => + + type SourceType = AbstractFile + type CreationType = ZipFile + + protected val creationSource = archive + protected lazy val root = new ZipRootCreator(_.parent)() + protected def DirEntryConstructor = new DirEntry(_, _, _) + protected def FileEntryConstructor = new FileEntry(_, _, _, _) + protected def ZipTravConstructor = zipTraversableFromZipFile _ + + abstract class Entry( + override val container: AbstractFile, + name: String, + path: String + ) extends VirtualFile(name, path) { + final override def path = "%s(%s)".format(self, pathInArchive) + final def getArchive = self.archive + def pathInArchive = super.path - val entries: Map[String, Entry] = new HashMap() - - var entry: ZipEntry = _ - - override def isDirectory = true - override def input = throw new Error("cannot read directories") - - override def lastModified: Long = - if (entry ne null) entry.getTime() else super.lastModified - - override def iterator: Iterator[AbstractFile] = entries.valuesIterator - - override def lookupName(name: String, directory: Boolean): AbstractFile = - entries.get(if (directory) name + "/" else name) match { - case Some(dir) => dir - case None => null - } + override def hashCode = super.hashCode + container.hashCode + override def equals(that : Any) = + super.equals(that) && (cond(that) { + case e: Entry => container == e.container + }) } - //######################################################################## - // Private Class - FileEntry + final class DirEntry( + container: AbstractFile, + name: String, + path: String + ) extends Entry(container, name, path) with DirEntryInterface + { + def source = container + } - /** A regular file archive entry */ - final class FileEntry(container : AbstractFile, name: String, path: String, val entry: ZipEntry) - extends Entry(container, name, path) { - def archive = ZipArchive.this.archive - override def lastModified: Long = entry.getTime() - override def input = archive.getInputStream(entry) - override def sizeOption = Some(entry.getSize().toInt) + final class FileEntry( + container: AbstractFile, + name: String, + path: String, + val entry: ZipEntry + ) extends Entry(container, name, path) with FileEntryInterface + { + def archive = self.archive + override def input = archive getInputStream entry } + + private def zipTraversableFromZipFile(z: ZipFile): ZipTrav = + new Traversable[ZipEntry] { + def zis: ZipInputStream = null // not valid for this type + val itStream = asIterator(z.entries()).toStream + def foreach[U](f: ZipEntry => U) = itStream foreach f + } } /** @@ -225,144 +254,53 @@ final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) * @author Stephane Micheloud * @version 1.0, 29/05/2007 */ -final class URLZipArchive(url: URL) extends AbstractFile { - assert(url ne null) +final class URLZipArchive(url: URL) extends AbstractFile with ZipContainer +{ + type SourceType = InputStream + type CreationType = InputStream - private var root: DirEntry = _ + protected lazy val creationSource = input + protected lazy val root = new ZipRootCreator(x => byteInputStream(x.traverser.zis))() - def container = throw new Error("unsupported") + protected def DirEntryConstructor = (_, name, path) => new DirEntry(name, path) + protected def FileEntryConstructor = new FileEntry(_, _, _, _) + protected def ZipTravConstructor = new ZipArchive.ZipEntryTraversableClass(_) def name: String = url.getFile() - def path: String = url.getPath() - - def file: File = null - + def input: InputStream = url.openStream() def absolute: AbstractFile = this - - def isDirectory: Boolean = true - def lastModified: Long = - try { url.openConnection().getLastModified() } - catch { case _ => 0 } - - /** Does this abstract file denote an existing file? */ - def create { - throw new UnsupportedOperationException - } - - /** Delete the underlying file or directory (recursively). */ - def delete { - throw new UnsupportedOperationException - } - - def input: InputStream = url.openStream() + try url.openConnection().getLastModified() + catch { case _: IOException => 0 } + /** Methods we don't support but have to implement because of the design */ + def file: JFile = null + def create: Unit = throw new UnsupportedOperationException + def delete: Unit = throw new UnsupportedOperationException def output = throw new Error("unsupported") + def container = throw new Error("unsupported") - override def iterator: Iterator[AbstractFile] = { - if (root eq null) load() - root.iterator - } - - override def lookupName(name: String, directory: Boolean): AbstractFile = { - if (root eq null) load() - root.lookupName(name, directory) - } - - /** Returns an abstract file with the given name. It does not - * check that it exists. - */ - def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = - throw new UnsupportedOperationException() - - private def load() { - def getEntryInputStream(in: InputStream): InputStream = { - val buf = new scala.collection.mutable.ArrayBuffer[Byte] - val data = new Array[Byte](1024) - var n = in.read(data) - while (n > 0) { - buf.++=(data, 0, n) - n = in.read(data) - } - new java.io.ByteArrayInputStream(buf.toArray) - } - this.root = new DirEntry("<root>", "/") - // A path to DirEntry map - val dirs: Map[String, DirEntry] = new HashMap() - dirs.update("/", root) - val zis = new ZipInputStream(input) - var entry = zis.getNextEntry() - while (entry ne null) { - val path = entry.getName() - assert(entry.isDirectory() == path.endsWith("/"), - this.toString() + " - " + path); - if (entry.isDirectory()) { - val dir: DirEntry = getDir(dirs, path) - assert(dir.entry eq null, this.toString() + " - " + path) - dir.entry = entry - } else { - val index = path.lastIndexOf('/') - val name = if (index < 0) path else path.substring(index + 1) - val home = if (index < 0) "/" else path.substring(0, index + 1) - val parent: DirEntry = getDir(dirs, home) - assert(!parent.entries.contains(path), this.toString() + " - " + path) - val in = getEntryInputStream(zis) - parent.entries.update(name, new FileEntry(name, path, entry, in)) - } - zis.closeEntry() - entry = zis.getNextEntry() - } + abstract class Entry(name: String, path: String) extends VirtualFile(name, path) { + final override def path = "%s(%s)".format(URLZipArchive.this, super.path) } - - private def getDir(dirs: Map[String, DirEntry], path: String): DirEntry = - dirs.get(path) match { - case Some(dir) => dir - case None => - val index = path.lastIndexOf('/', path.length() - 2) - val name = if (index < 0) path else path.substring(index + 1) - val home = if (index < 0) "/" else path.substring(0, index + 1) - val parent: DirEntry = getDir(dirs, home) - val dir = new DirEntry(name.substring(0, name.length() - 1), path) - parent.entries.update(name, dir) - dirs.update(path, dir) - dir - } - - /** Superclass of archive entries */ - abstract class Entry(name: String, path: String) - extends VirtualFile(name, path) { - final override def path = URLZipArchive.this.toString() + "(" + super.path + ")" - //final def getArchive = URLZipArchive.this.archive + final class DirEntry(name: String, path: String) extends Entry(name, path) with DirEntryInterface { + def source = input } - - /** A directory archive entry */ - private final class DirEntry(name: String, path: String) - extends Entry(name, path) + final class FileEntry( + val in: InputStream, + name: String, + path: String, + val entry: ZipEntry + ) extends Entry(name, path) with FileEntryInterface { - val entries: Map[String, Entry] = new HashMap() - var entry: ZipEntry = _ - - override def isDirectory = true - override def input = throw new Error("cannot read directories") - - override def lastModified: Long = - if (entry ne null) entry.getTime() else super.lastModified - - override def iterator: Iterator[AbstractFile] = entries.valuesIterator - - override def lookupName(name: String, directory: Boolean): AbstractFile = - entries.get(if (directory) name + "/" else name) match { - case Some(dir) => dir - case None => null - } + override def input = in } - final class FileEntry(name: String, path: String, - val entry: ZipEntry, val in: InputStream) - extends Entry(name, path) { - override def lastModified: Long = entry.getTime() - override def input = in - override def sizeOption = Some(entry.getSize().toInt) + /** Private methods **/ + private def byteInputStream(in: InputStream): InputStream = { + val buf = new BufferedInputStream(in) + val bytes = Iterator continually in.read().toByte takeWhile (_ != -1) + new ByteArrayInputStream(bytes.toSequence.toArray) } } diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala index 3013199c36..2b61724c49 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package plugins -import java.io.File +import io.{ File, Path } import java.net.URLClassLoader import java.util.jar.JarFile import java.util.zip.ZipException @@ -63,39 +63,44 @@ abstract class Plugin { * @author Lex Spoon * @version 1.0, 2007-5-21 */ -object Plugin { +object Plugin +{ + private val PluginXML = "scalac-plugin.xml" + /** Create a class loader with the specified file plus * the loader that loaded the Scala compiler. */ - private def loaderFor(jarfiles: Seq[File]): ClassLoader = { + private def loaderFor(jarfiles: Seq[Path]): ClassLoader = { val compilerLoader = classOf[Plugin].getClassLoader - val jarurls = jarfiles.map(_.toURL).toArray - new URLClassLoader(jarurls, compilerLoader) + val jarurls = jarfiles map (_.toURL) + + new URLClassLoader(jarurls.toArray, compilerLoader) } /** Try to load a plugin description from the specified * file, returning None if it does not work. */ - private def loadDescription(jarfile: File): Option[PluginDescription] = { - if (!jarfile.exists) return None + private def loadDescription(jarfile: Path): Option[PluginDescription] = + // XXX Return to this once we have some ARM support + if (!jarfile.exists) None + else try { + val jar = new JarFile(jarfile.jfile) - try { - val jar = new JarFile(jarfile) try { - val ent = jar.getEntry("scalac-plugin.xml") - if (ent == null) return None - - val inBytes = jar.getInputStream(ent) - val packXML = XML.load(inBytes) - inBytes.close() - - PluginDescription.fromXML(packXML) - } finally { - jar.close() + (jar getEntry PluginXML) match { + case null => None + case entry => + val in = jar getInputStream entry + val packXML = XML load in + in.close() + + PluginDescription fromXML packXML + } } - } catch { + finally jar.close() + } + catch { case _: ZipException => None } - } type AnyClass = Class[_] @@ -103,16 +108,13 @@ object Plugin { * if the jar file has no plugin in it or if the plugin * is badly formed. */ - def loadFrom(jarfile: File, loader: ClassLoader): Option[AnyClass] = { + def loadFrom(jarfile: Path, loader: ClassLoader): Option[AnyClass] = { val pluginInfo = loadDescription(jarfile).get - try { - Some(loader.loadClass(pluginInfo.classname)) - } catch { - case _:ClassNotFoundException => - println("Warning: class not found for plugin in " + jarfile + - " (" + pluginInfo.classname + ")") - None + try Some(loader loadClass pluginInfo.classname) catch { + case _: ClassNotFoundException => + println("Warning: class not found for plugin in %s (%s)".format(jarfile, pluginInfo.classname)) + None } } @@ -121,35 +123,28 @@ object Plugin { * directories specified. Skips all plugins in <code>ignoring</code>. * A single classloader is created and used to load all of them. */ - def loadAllFrom(jars: List[File], - dirs: List[File], - ignoring: List[String]): List[AnyClass] = + def loadAllFrom( + jars: List[Path], + dirs: List[Path], + ignoring: List[String]): List[AnyClass] = { - val alljars = new ListBuffer[File] - - alljars ++= jars - - for { + val alljars = jars ::: (for { dir <- dirs if dir.isDirectory - entries = dir.listFiles - if entries ne null - entry <- entries.toList.sort(_.getName <= _.getName) - if entry.toString.toLowerCase endsWith ".jar" + entry <- dir.toDirectory.files.toList sortWith (_.name <= _.name) + if entry.name.toLowerCase endsWith ".jar" pdesc <- loadDescription(entry) if !(ignoring contains pdesc.name) - } alljars += entry + } yield entry) - val loader = loaderFor(alljars.toList) - alljars.toList.map(f => loadFrom(f,loader)).flatMap(x => x) + val loader = loaderFor(alljars) + alljars map (loadFrom(_, loader)) flatten } /** Instantiate a plugin class, given the class and * the compiler it is to be used in. */ def instantiate(clazz: AnyClass, global: Global): Plugin = { - //println("instantiating "+clazz) - //println(clazz.getDeclaredConstructors) - val constructor = clazz.getConstructor(classOf[Global]) - constructor.newInstance(global).asInstanceOf[Plugin] + val constructor = clazz getConstructor classOf[Global] + (constructor newInstance global).asInstanceOf[Plugin] } } diff --git a/src/compiler/scala/tools/nsc/plugins/Plugins.scala b/src/compiler/scala/tools/nsc/plugins/Plugins.scala index 1163ae5a6a..93a3f46ac2 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugins.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugins.scala @@ -8,7 +8,7 @@ package scala.tools.nsc package plugins -import java.io.File +import io.{ File, Path } /** Support for run-time loading of compiler plugins. * @@ -16,7 +16,9 @@ import java.io.File * @version 1.1, 2009/1/2 * Updated 2009/1/2 by Anders Bach Nielsen: Added features to implement SIP 00002 */ -trait Plugins { self: Global => +trait Plugins +{ + self: Global => /** Load a rough list of the plugins. For speed, it * does not instantiate a compiler run. Therefore it cannot @@ -24,24 +26,17 @@ trait Plugins { self: Global => * filtered from the final list of plugins. */ protected def loadRoughPluginsList(): List[Plugin] = { - val jars = settings.plugin.value.map(new File(_)) - val dirs = - for (name <- settings.pluginsDir.value.split(File.pathSeparator).toList) - yield new File(name) + val jars = settings.plugin.value map Path.apply + val dirs = (settings.pluginsDir.value split File.pathSeparator).toList map Path.apply + val classes = Plugin.loadAllFrom(jars, dirs, settings.disable.value) + + classes foreach (c => Plugin.instantiate(c, this)) for (plugClass <- Plugin.loadAllFrom(jars, dirs, settings.disable.value)) yield Plugin.instantiate(plugClass, this) } - private var roughPluginsListCache: Option[List[Plugin]] = None - - protected def roughPluginsList: List[Plugin] = - roughPluginsListCache match { - case Some(list) => list - case None => - roughPluginsListCache = Some(loadRoughPluginsList) - roughPluginsListCache.get - } + protected lazy val roughPluginsList: List[Plugin] = loadRoughPluginsList /** Load all available plugins. Skips plugins that * either have the same name as another one, or which @@ -54,105 +49,70 @@ trait Plugins { self: Global => plugNames: Set[String], phaseNames: Set[String]): List[Plugin] = { - plugins match { - case Nil => Nil - case plug :: rest => - val plugPhaseNames = Set.empty ++ plug.components.map(_.phaseName) - def withoutPlug = pick(rest, plugNames, plugPhaseNames) - def withPlug = - (plug :: - pick(rest, - plugNames+plug.name, - phaseNames++plugPhaseNames)) - - if (plugNames.contains(plug.name)) { - if (settings.verbose.value) - inform("[skipping a repeated plugin: " + plug.name + "]") - withoutPlug - } else if (settings.disable.value contains(plug.name)) { - if (settings.verbose.value) - inform("[disabling plugin: " + plug.name + "]") - withoutPlug - } else { - val commonPhases = phaseNames.intersect(plugPhaseNames) - if (!commonPhases.isEmpty) { - if (settings.verbose.value) - inform("[skipping plugin " + plug.name + - "because it repeats phase names: " + - commonPhases.mkString(", ") + "]") - withoutPlug - } else { - if (settings.verbose.value) - inform("[loaded plugin " + plug.name + "]") - withPlug - } - } + if (plugins.isEmpty) return Nil // early return + + val plug :: tail = plugins + val plugPhaseNames = Set(plug.components map (_.phaseName): _*) + def withoutPlug = pick(tail, plugNames, plugPhaseNames) + def withPlug = plug :: pick(tail, plugNames + plug.name, phaseNames ++ plugPhaseNames) + lazy val commonPhases = phaseNames intersect plugPhaseNames + + def note(msg: String): Unit = if (settings.verbose.value) inform(msg format plug.name) + def fail(msg: String) = { note(msg) ; withoutPlug } + + if (plugNames contains plug.name) + fail("[skipping a repeated plugin: %s]") + else if (settings.disable.value contains plug.name) + fail("[disabling plugin: %s]") + else if (!commonPhases.isEmpty) + fail("[skipping plugin %s because it repeats phase names: " + (commonPhases mkString ", ") + "]") + else { + note("[loaded plugin %s]") + withPlug } } - val plugs = - pick(roughPluginsList, - Set.empty, - Set.empty ++ phasesSet.map(_.phaseName)) + val plugs = pick(roughPluginsList, Set(), phasesSet map (_.phaseName) toSet) - for (req <- settings.require.value; if !plugs.exists(p => p.name==req)) + /** Verify requirements are present. */ + for (req <- settings.require.value ; if !(plugs exists (_.name == req))) error("Missing required plugin: " + req) + /** Process plugin options. */ + def namec(plug: Plugin) = plug.name + ":" + def optList(xs: List[String], p: Plugin) = xs filter (_ startsWith namec(p)) + def doOpts(p: Plugin): List[String] = + optList(settings.pluginOptions.value, p) map (_ stripPrefix namec(p)) - for (plug <- plugs) { - val nameColon = plug.name + ":" - val opts = for { - raw <- settings.pluginOptions.value - if raw.startsWith(nameColon) - } yield raw.substring(nameColon.length) - + for (p <- plugs) { + val opts = doOpts(p) if (!opts.isEmpty) - plug.processOptions(opts, error) + p.processOptions(opts, error) } - for { - opt <- settings.pluginOptions.value - if !plugs.exists(p => opt.startsWith(p.name + ":")) - } error("bad option: -P:" + opt) + /** Verify no non-existent plugin given with -P */ + for (opt <- settings.pluginOptions.value ; if plugs forall (p => optList(List(opt), p).isEmpty)) + error("bad option: -P:" + opt) plugs } - private var pluginsCache: Option[List[Plugin]] = None - - def plugins: List[Plugin] = { - if (pluginsCache.isEmpty) - pluginsCache = Some(loadPlugins) - pluginsCache.get - } + lazy val plugins: List[Plugin] = loadPlugins /** A description of all the plugins that are loaded */ - def pluginDescriptions: String = { - val messages = - for (plugin <- roughPluginsList) - yield plugin.name + " - " + plugin.description - messages.mkString("\n") - } + def pluginDescriptions: String = + roughPluginsList map (x => "%s - %s".format(x.name, x.description)) mkString "\n" /** * Extract all phases supplied by plugins and add them to the phasesSet. * @see phasesSet */ - protected def computePluginPhases() { - val plugPhases = plugins.flatMap(_.components) - for (pPhase <- plugPhases) { - phasesSet += pPhase - } - } + protected def computePluginPhases(): Unit = + phasesSet ++= (plugins flatMap (_.components)) /** Summary of the options for all loaded plugins */ - def pluginOptionsHelp: String = { - val buf = new StringBuffer - for (plug <- roughPluginsList; help <- plug.optionsHelp) { - buf append ("Options for plugin " + plug.name + ":\n") - buf append help - buf append "\n" - } - buf.toString - } + def pluginOptionsHelp: String = + (for (plug <- roughPluginsList ; help <- plug.optionsHelp) yield { + "Options for plugin %s:\n%s\n".format(plug.name, help) + }) mkString } |