diff options
author | Paul Phillips <paulp@improving.org> | 2009-09-11 18:12:33 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2009-09-11 18:12:33 +0000 |
commit | e604abb25c1c95aa75b969519aea3a915ba11798 (patch) | |
tree | 05081680122b722e0d62c2b34cbc18cc7c5019ed | |
parent | f9394a4d472887c4563e768467170a9b1677d5c1 (diff) | |
download | scala-e604abb25c1c95aa75b969519aea3a915ba11798.tar.gz scala-e604abb25c1c95aa75b969519aea3a915ba11798.tar.bz2 scala-e604abb25c1c95aa75b969519aea3a915ba11798.zip |
Resurrected the former contents of scala.io in ...
Resurrected the former contents of scala.io in their new home,
scala.tools.nsc.io, and equipped them with fake beards and handlebar
moustaches. Also restored the reverted bits of the compiler which had
been taking advantage of them.
23 files changed, 1301 insertions, 927 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 } diff --git a/src/dotnet-library/scala/io/File.scala b/src/dotnet-library/scala/io/File.scala deleted file mode 100644 index e57b4fb979..0000000000 --- a/src/dotnet-library/scala/io/File.scala +++ /dev/null @@ -1 +0,0 @@ -/* File.scala does not exist for the dotnet target */
\ No newline at end of file diff --git a/src/dotnet-library/scala/io/Process.scala b/src/dotnet-library/scala/io/Process.scala deleted file mode 100644 index dda8821199..0000000000 --- a/src/dotnet-library/scala/io/Process.scala +++ /dev/null @@ -1 +0,0 @@ -/* Process.scala does not exist for the dotnet target */
\ No newline at end of file diff --git a/src/dotnet-library/scala/io/Streamable.scala b/src/dotnet-library/scala/io/Streamable.scala deleted file mode 100644 index ff90ea3bbb..0000000000 --- a/src/dotnet-library/scala/io/Streamable.scala +++ /dev/null @@ -1 +0,0 @@ -/* Streamable.scala does not exist for the dotnet target */
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/nest/DirectRunner.scala b/src/partest/scala/tools/partest/nest/DirectRunner.scala index 3cc84b8d89..c087b58c69 100644 --- a/src/partest/scala/tools/partest/nest/DirectRunner.scala +++ b/src/partest/scala/tools/partest/nest/DirectRunner.scala @@ -11,6 +11,7 @@ package nest import java.io.{File, PrintStream, FileOutputStream, BufferedReader, InputStreamReader, StringWriter, PrintWriter} import java.util.StringTokenizer +import scala.tools.nsc.io.Directory import scala.actors.Actor._ import scala.actors.TIMEOUT @@ -66,13 +67,9 @@ trait DirectRunner { fails += 1 } } - logsToDelete.foreach { log => - NestUI.verbose("deleting "+log) - fileManager.deleteRecursive(log) - } - outdirsToDelete.foreach { outdir => - NestUI.verbose("deleting "+outdir) - fileManager.deleteRecursive(outdir) + for (x <- logsToDelete ::: outdirsToDelete) { + NestUI.verbose("deleting "+x) + Directory(x).deleteRecursively() } (succs, fails) diff --git a/src/partest/scala/tools/partest/nest/FileManager.scala b/src/partest/scala/tools/partest/nest/FileManager.scala index bdce6c8439..238681bfc6 100644 --- a/src/partest/scala/tools/partest/nest/FileManager.scala +++ b/src/partest/scala/tools/partest/nest/FileManager.scala @@ -10,6 +10,7 @@ package nest import java.io.{File, FilenameFilter, IOException, StringWriter} import java.net.URI +import scala.tools.nsc.io.Directory trait FileManager { @@ -18,12 +19,7 @@ trait FileManager { if (inx < 0) name else name.substring(0, inx) } - def deleteRecursive(dir: File) { - if (dir.isDirectory) { - for (file <- dir.list) deleteRecursive(new File(dir, file)) - } - dir.delete - } + def deleteRecursive(dir: File) { Directory(dir).deleteRecursively() } /** * Compares two files using a Java implementation of the GNU diff |