From 9636749e637088f8ba149b73620535cd1992614a Mon Sep 17 00:00:00 2001 From: Iulian Dragos Date: Fri, 29 May 2009 14:16:24 +0000 Subject: Refactored the existing dependency tracker and ... Refactored the existing dependency tracker and added a 'build manager' interface for IDE use. --- src/compiler/scala/tools/nsc/BuildManager.scala | 67 ++++++++ src/compiler/scala/tools/nsc/Global.scala | 23 ++- .../scala/tools/nsc/SimpleBuildManager.scala | 66 ++++++++ .../scala/tools/nsc/dependencies/Changes.scala | 64 ++++++++ .../nsc/dependencies/DependencyAnalysis.scala | 58 ++++--- .../scala/tools/nsc/dependencies/Files.scala | 179 +++++++++------------ .../scala/tools/nsc/dependencies/References.scala | 34 ++++ src/compiler/scala/tools/nsc/io/AbstractFile.scala | 38 ++++- src/compiler/scala/tools/nsc/io/PlainFile.scala | 28 +++- .../scala/tools/nsc/io/VirtualDirectory.scala | 19 +++ src/compiler/scala/tools/nsc/io/VirtualFile.scala | 18 +++ src/compiler/scala/tools/nsc/io/ZipArchive.scala | 24 +++ 12 files changed, 478 insertions(+), 140 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/BuildManager.scala create mode 100644 src/compiler/scala/tools/nsc/SimpleBuildManager.scala create mode 100644 src/compiler/scala/tools/nsc/dependencies/Changes.scala create mode 100644 src/compiler/scala/tools/nsc/dependencies/References.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/BuildManager.scala b/src/compiler/scala/tools/nsc/BuildManager.scala new file mode 100644 index 0000000000..6ee1e6547b --- /dev/null +++ b/src/compiler/scala/tools/nsc/BuildManager.scala @@ -0,0 +1,67 @@ +package scala.tools.nsc + +import scala.collection._ + +import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} +import util.FakePos + +import dependencies._ +import nsc.io.AbstractFile + +trait BuildManager { + + /** Add the given source files to the managed build process. */ + def addSourceFiles(files: Set[AbstractFile]) + + /** Remove the given files from the managed build process. */ + def removeFiles(files: Set[AbstractFile]) + + /** The given files have been modified by the user. Recompile + * them and their dependent files. + */ + def update(files: Set[AbstractFile]) + + /** Load saved dependency information. */ + def loadFrom(file: AbstractFile) + + /** Save dependency information to `file'. */ + def saveTo(file: AbstractFile) + + def compiler: Global +} + + + +/** Simple driver for testing the build manager. It presents + * the user to a 'resident compiler' prompt. Each line is + * interpreted as a set of files that have changed. The builder + * then derives the dependent files and recompiles them. + */ +object BuildManagerTest extends EvalLoop { + + def prompt = "builder > " + + def error(msg: String) { + println(msg + "\n scalac -help gives more information") + } + + def main(args: Array[String]) { + implicit def filesToSet(fs: List[String]): Set[AbstractFile] = + Set.empty ++ (fs map AbstractFile.getFile) + + val settings = new Settings(error) + val command = new CompilerCommand(List.fromArray(args), settings, error, false) +// settings.make.value = "off" + val buildManager: BuildManager = new SimpleBuildManager(settings) + + buildManager.addSourceFiles(command.files) + + // enter resident mode + loop { line => + val args = List.fromString(line, ' ') + val command = new CompilerCommand(args, new Settings(error), error, true) + buildManager.update(command.files) + } + + } +} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 77e8ec4869..71fe679902 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -18,7 +18,7 @@ import scala.collection.mutable.{HashSet, HashMap, ListBuffer} import symtab._ import symtab.classfile.{PickleBuffer, Pickler} -import dependencies.DependencyAnalysis +import dependencies.{DependencyAnalysis} import util.Statistics import plugins.Plugins import ast._ @@ -217,6 +217,17 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable } } + settings.dependenciesFile.value match { + case "none" => () + case x => + val jfile = new java.io.File(x) + if (!jfile.exists) jfile.createNewFile + + dependencyAnalysis.loadFrom(AbstractFile.getFile(jfile)) + } + + + lazy val classPath0 = new ClassPath(false && onlyPresentation) lazy val classPath = @@ -567,12 +578,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable if (forJVM) { phasesSet += liftcode // generate reified trees phasesSet += genJVM // generate .class files - if (!dependencyAnalysis.off){ - if(settings.debug.value){ - println("Adding dependency analysis phase"); - } - phasesSet += dependencyAnalysis - } + phasesSet += dependencyAnalysis } if (forMSIL) { phasesSet += genMSIL // generate .msil files @@ -811,7 +817,8 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable for ((sym, file) <- symSource.iterator) resetPackageClass(sym.owner) informTime("total", startTime) - dependencyAnalysis.writeToFile(); + if (!dependencyAnalysis.off) + dependencyAnalysis.saveDependencies() } /** Compile list of abstract files */ diff --git a/src/compiler/scala/tools/nsc/SimpleBuildManager.scala b/src/compiler/scala/tools/nsc/SimpleBuildManager.scala new file mode 100644 index 0000000000..4f8d5948d2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/SimpleBuildManager.scala @@ -0,0 +1,66 @@ +package scala.tools.nsc + +import scala.collection._ + +import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} +import dependencies._ + +import util.FakePos +import nsc.io.AbstractFile + +/** A simple build manager, using the default scalac dependency tracker. + * The transitive closure of all dependent files on a modified file + * is recompiled at once. + * + * It is equivalent to using a resident compiler mode with the + * '-make:transitive' option. + */ +class SimpleBuildManager(val settings: Settings) extends BuildManager { + + val compiler: Global = new Global(settings) + + /** Managed source files. */ + private val sources: mutable.Set[AbstractFile] = new mutable.HashSet[AbstractFile] + + /** Add the given source files to the managed build process. */ + def addSourceFiles(files: Set[AbstractFile]) { + sources ++= files + update(files) + } + + /** Remove the given files from the managed build process. */ + def removeFiles(files: Set[AbstractFile]) { + sources --= files + } + + /** The given files have been modified by the user. Recompile + * them and all files that depend on them. Only files that + * have been previously added as source files are recompiled. + */ + def update(files: Set[AbstractFile]) { + val deps = compiler.dependencyAnalysis.dependencies + val run = new compiler.Run() + compiler.inform("compiling " + files) + + val toCompile = + (files ++ deps.dependentFiles(Int.MaxValue, files)) intersect sources + + + compiler.inform("Recompiling " + + (if(settings.debug.value) toCompile.mkString(", ") + else toCompile.size + " files")) + + run.compileFiles(files.toList) + } + + /** Load saved dependency information. */ + def loadFrom(file: AbstractFile) { + compiler.dependencyAnalysis.loadFrom(file) + } + + /** Save dependency information to `file'. */ + def saveTo(file: AbstractFile) { + compiler.dependencyAnalysis.dependenciesFile = file + compiler.dependencyAnalysis.saveDependencies() + } +} diff --git a/src/compiler/scala/tools/nsc/dependencies/Changes.scala b/src/compiler/scala/tools/nsc/dependencies/Changes.scala new file mode 100644 index 0000000000..bf2c61a46c --- /dev/null +++ b/src/compiler/scala/tools/nsc/dependencies/Changes.scala @@ -0,0 +1,64 @@ +package scala.tools.nsc.dependencies + +import collection._ + +/** A component that describes the possible changes between successive + * compilations of a class. + */ +abstract class Changes { + + /** A compiler instance used to compile files on demand. */ + val compiler: Global + + import compiler._ + import symtab.Flags._ + + abstract class Change + + /** Modifiers of target have changed */ + case class Mods(from: Long, to: Long)(target: Symbol) extends Change { + def moreRestrictive: Boolean = + ((((to & PRIVATE) != 0) && (from & PRIVATE) == 0) + || (((to & PROTECTED) != 0) && (from & PROTECTED) == 0)) + + def morePermissive: Boolean = !moreRestrictive + } + + case class Parents(target: Symbol) extends Change + case class TypeParams(from: List[Symbol], to: List[Symbol]) extends Change + case class Added(member: String) extends Change + case class Removed(member: String) extends Change + case class Changed(member: String)extends Change + + + /** Return the list of changes between 'from' and 'to'. + */ + def changeSet(from: Symbol, to: Symbol): List[Change] = { + val cs = new mutable.ListBuffer[Change] + + if ((from.info.parents intersect to.info.parents).size + != from.info.parents.size) + cs += Parents(from) + + if (from.typeParams != to.typeParams) + cs += TypeParams(from.typeParams, to.typeParams) + + // new members not yet visited + val newMembers = mutable.HashSet[String]() + (to.info.decls.elements) foreach (newMembers += _.fullNameString) + + for (o <- from.info.decls.elements; + val n = to.info.decl(o.name)) { + newMembers -= n.fullNameString + + if (n == NoSymbol) + cs += Removed(o.fullNameString) + else if (n.suchThat(_.tpe == o.tpe) == NoSymbol) + cs += Changed(o.fullNameString) + } + cs ++= (newMembers map Added) + + cs.toList + } + +} diff --git a/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala index 5004086c1a..48b22677b2 100644 --- a/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala +++ b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala @@ -1,7 +1,8 @@ package scala.tools.nsc.dependencies; import util.SourceFile; +import nsc.io.AbstractFile -trait DependencyAnalysis extends SubComponent with Files{ +trait DependencyAnalysis extends SubComponent with Files { import global._ val phaseName = "dependencyAnalysis"; @@ -16,34 +17,41 @@ trait DependencyAnalysis extends SubComponent with Files{ case "immediate" => 1 } - def nameToFile(name : Any) = - settings.outdir.value / (name.toString.replace(".", java.io.File.separator) + ".class") + def nameToFile(src: AbstractFile, name : String) = + settings.outputDirs.outputDirFor(src) + .lookupPathUnchecked(name.toString.replace(".", java.io.File.separator) + ".class", false) - lazy val dependenciesFile : Option[File] = settings.dependenciesFile.value match { - case "none" => None - case x => Some(toFile(x)) + private var depFile: Option[AbstractFile] = None + + def dependenciesFile_=(file: AbstractFile) { + assert(file ne null) + depFile = Some(file) } + def dependenciesFile: Option[AbstractFile] = depFile + def classpath = settings.classpath.value def newDeps = new FileDependencies(classpath); - lazy val dependencies = - dependenciesFile match { - case Some(f) if f.exists => { - val fd = FileDependencies.readFrom(f); - if (fd.classpath != classpath) { - if(settings.debug.value){ - println("Classpath has changed. Nuking dependencies"); - } - newDeps - } - else fd - } - case _ => newDeps; - } + var dependencies = newDeps + + /** Write dependencies to the current file. */ + def saveDependencies() = + if(dependenciesFile.isDefined) + dependencies.writeTo(dependenciesFile.get) - def writeToFile() = if(!off){ - dependenciesFile.foreach(dependencies.writeTo(_)); + /** Load dependencies from the given file and save the file reference for + * future saves. + */ + def loadFrom(f: AbstractFile) { + dependenciesFile = f + val fd = FileDependencies.readFrom(f); + dependencies = if (fd.classpath != classpath) { + if(settings.debug.value){ + println("Classpath has changed. Nuking dependencies"); + } + newDeps + } else fd } def filter(files : List[SourceFile]) : List[SourceFile] = @@ -77,13 +85,13 @@ trait DependencyAnalysis extends SubComponent with Files{ // they have no source file. We simply ignore this case // as irrelevant to dependency analysis. if (f != null){ - val source : File = unit.source.file.file; + val source: AbstractFile = unit.source.file; for (d <- unit.icode){ - dependencies.emits(source, nameToFile(d)) + dependencies.emits(source, nameToFile(unit.source.file, d.toString)) } for (d <- unit.depends; if (d.sourceFile != null)){ - dependencies.depends(source, d.sourceFile.file); + dependencies.depends(source, d.sourceFile); } } } diff --git a/src/compiler/scala/tools/nsc/dependencies/Files.scala b/src/compiler/scala/tools/nsc/dependencies/Files.scala index e2bd341c47..7d52206392 100644 --- a/src/compiler/scala/tools/nsc/dependencies/Files.scala +++ b/src/compiler/scala/tools/nsc/dependencies/Files.scala @@ -1,18 +1,32 @@ package scala.tools.nsc.dependencies; -import java.io.{File => JFile, _} +import java.io.{InputStream, OutputStream, PrintStream, InputStreamReader, BufferedReader} +import nsc.io.{AbstractFile, PlainFile} -import scala.collection.mutable._; +import scala.collection._; -trait Files{ +trait Files { - implicit def toFile(name : String) : File = toFile(new JFile(name)); - implicit def toFile(jf : JFile) : File = new File(jf); + /** Resolve the given name to a file. */ + implicit def toFile(name: String): AbstractFile = { + val file = rootDirectory.lookupPathUnchecked(name, false) + assert(file ne null, name) + file + } + + /** The directory where file lookup should start at. */ + var rootDirectory: AbstractFile = { + AbstractFile.getDirectory(".") +// val roots = java.io.File.listRoots() +// assert(roots.length > 0) +// new PlainFile(roots(0)) + } - class FileDependencies(val classpath : String){ - class Tracker extends OpenHashMap[File, Set[File]]{ - override def default(key : File) = { - this(key) = new HashSet[File]; + class FileDependencies(val classpath : String) { + + class Tracker extends mutable.OpenHashMap[AbstractFile, mutable.Set[AbstractFile]]{ + override def default(key: AbstractFile) = { + this(key) = new mutable.HashSet[AbstractFile]; this(key); } } @@ -22,20 +36,22 @@ trait Files{ def isEmpty = dependencies.isEmpty && targets.isEmpty - def emits(source : File, result : File) = targets(source.absolute) += result.absolute; - def depends(from : File, on : File) = dependencies(from.absolute) += on.absolute; + def emits(source: AbstractFile, result: AbstractFile) = + targets(source) += result; + def depends(from: AbstractFile, on: AbstractFile) = + dependencies(from) += on; - def reset(file : File) = dependencies -= file; + def reset(file: AbstractFile) = dependencies -= file; def cleanEmpty() = { dependencies.foreach({case (key, value) => value.retain(_.exists)}) dependencies.retain((key, value) => key.exists && !value.isEmpty) } - def containsFile(f : File) = targets.contains(f.absolute) + def containsFile(f: AbstractFile) = targets.contains(f.absolute) def invalidatedFiles(maxDepth : Int) = { - val direct = new HashSet[File]; + val direct = new mutable.HashSet[AbstractFile]; for ((file, products) <- targets) { // This looks a bit odd. It may seem like one should invalidate a file @@ -45,12 +61,25 @@ trait Files{ direct(file) ||= products.forall(d => d.lastModified < file.lastModified) } + val indirect = dependentFiles(maxDepth, direct) - val seen = new HashSet[File]; - val indirect = new HashSet[File]; - val newInvalidations = new HashSet[File]; + for ((source, targets) <- targets; + if direct(source) || indirect(source)){ + targets.foreach(_.delete); + targets -= source; + } - def invalid(file : File) = indirect(file) || direct(file); + (direct, indirect); + } + + /** Return the sef of files that depend on the given changed files. + * It computes the transitive closure up to the given depth. + */ + def dependentFiles(depth: Int, changed: Set[AbstractFile]): Set[AbstractFile] = { + val indirect = new mutable.HashSet[AbstractFile]; + val newInvalidations = new mutable.HashSet[AbstractFile]; + + def invalid(file: AbstractFile) = indirect(file) || changed(file); def go(i : Int) : Unit = if(i > 0){ newInvalidations.clear; @@ -64,25 +93,26 @@ trait Files{ else () } - go(maxDepth) - - indirect --= direct + go(depth) - for ((source, targets) <- targets; if (invalid(source))){ - targets.foreach(_.rm); - targets -= source; - } + indirect --= changed + } - (direct, indirect); + def writeTo(file: AbstractFile) { + writeToFile(file)(out => writeTo(new PrintStream(out))) } - def writeTo(file : File) : Unit = file.writeTo(out => writeTo(new PrintStream(out))); def writeTo(print : PrintStream) : Unit = { + def prettify(path: String): String = + if (path.startsWith("./")) + path.substring(2, path.length) + else path + cleanEmpty(); def emit(tracker : Tracker){ for ((f, ds) <- tracker; d <- ds){ - print.println(f + " -> " + d); + print.println(prettify(f.toString) + " -> " + prettify(d.toString)); } } @@ -95,10 +125,12 @@ trait Files{ } } + + object FileDependencies{ val Separator = "-------"; - def readFrom(file : File) = file.readFrom(in => { + def readFrom(file: AbstractFile): FileDependencies = readFromFile(file) { in => val reader = new BufferedReader(new InputStreamReader(in)); val it = new FileDependencies(reader.readLine); reader.readLine; @@ -118,88 +150,27 @@ trait Files{ } it; - }) - } - - - def currentDirectory = new File(new JFile(".")) - - case class File private[Files](val underlying : JFile){ - if (underlying == null) throw new NullPointerException(); - - def absolute : File = underlying.getAbsoluteFile; - def canonical : File = underlying.getCanonicalFile - - def assertDirectory = - if (exists && !isDirectory) error(this + " is not a directory") - else this; - - def assertExists = - if (!exists) error(this + " does not exist") - else this; - - def lastModified = underlying.lastModified - - def list : Iterable[File] = - assertExists.assertDirectory.underlying.listFiles.view.map(toFile) - - def / (file : File) : File = - new JFile(assertDirectory.toString, - file.toString) - - override def toString = { - val it = underlying.getPath; - if (it.length == 0) "." - else it - } - - def exists = underlying.exists - def isDirectory = underlying.isDirectory; - - def parent : File = { - val x = underlying.getParentFile; - if (x == null) currentDirectory - else x } + } - def create : Boolean = {parent.mkdir; underlying.createNewFile } - def mkdir : Boolean = - if (exists) { assertDirectory; false } - else {parent.mkdir; underlying.mkdir; } - - def rm : Boolean = { - if (isDirectory) list.foreach(_.rm); - underlying.delete; - } - - def descendants : Iterable[File] = - list.flatMap(x => if (x.isDirectory) x.descendants else List(x)) - - def extension = { - val name = toString; - val i = name.lastIndexOf('.'); - if (i == -1) "" else name.substring(i + 1) - } - def writeTo[T](f : OutputStream => T) : T = { - val out = new FileOutputStream(underlying); - try { - f(out); - } finally { - out.close; - } + def writeToFile[T](file: AbstractFile)(f: OutputStream => T) : T = { + val out = file.output + try { + f(out); + } finally { + out.close; } + } - def readFrom[T](f : InputStream => T) : T = { - val in = new FileInputStream(underlying); - try{ - f(in); - } finally { - in.close; - } + def readFromFile[T](file: AbstractFile)(f: InputStream => T) : T = { + val in = file.input + try{ + f(in); + } finally { + in.close; } } - } object Files extends Files; diff --git a/src/compiler/scala/tools/nsc/dependencies/References.scala b/src/compiler/scala/tools/nsc/dependencies/References.scala new file mode 100644 index 0000000000..89fa71f6c1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/dependencies/References.scala @@ -0,0 +1,34 @@ +package scala.tools.nsc.dependencies; +import util.SourceFile; + +import collection._ + +abstract class References extends SubComponent with Files { + import global._ + + val phaseName = "references analysis"; + + def newPhase(prev: Phase) = new ReferenceAnalysisPhase(prev) + + /** Top level definitions per source file. */ + val definitions: mutable.Map[String, List[Symbol]] = new mutable.HashMap + + class ReferenceAnalysisPhase(prev: Phase) extends StdPhase(prev) { + def apply(unit: global.CompilationUnit) { + val buf = new mutable.ListBuffer[Symbol] + (new Traverser { + override def traverse(tree: Tree) { + tree match { + case cdef: ClassDef if !cdef.symbol.isModuleClass => + buf += cdef.symbol.cloneSymbol + case _ => + super.traverse(tree) + } + } + }).apply(unit.body) + + definitions(unit.source.file.path) = buf.toList + } + } +} + diff --git a/src/compiler/scala/tools/nsc/io/AbstractFile.scala b/src/compiler/scala/tools/nsc/io/AbstractFile.scala index 7414a6ca27..70121aefb9 100644 --- a/src/compiler/scala/tools/nsc/io/AbstractFile.scala +++ b/src/compiler/scala/tools/nsc/io/AbstractFile.scala @@ -102,12 +102,26 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { /** Returns the path of this abstract file. */ def path: String + /** The absolute file, if this is a relative file. */ + def absolute: AbstractFile + /** Returns the containing directory of this abstract file */ def container : AbstractFile /** Returns the underlying File if any and null otherwise. */ def file: File + /** Does this abstract file denote an existing file? */ + def exists: Boolean = + if (file ne null) file.exists() + else true + + /** Create a file on disk, if one does not exist already. */ + def create: Unit + + /** Delete the underlying file or directory (recursively). */ + def delete: Unit + /** Is this abstract file a directory? */ def isDirectory: Boolean @@ -164,6 +178,11 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { */ def lookupName(name: String, directory: Boolean): AbstractFile + /** Returns an abstract file with the given name. It does not + * check that it exists. + */ + def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile + /** Returns the abstract file in this abstract directory with the specified * path relative to it, If there is no such file, returns null. The argument * directory tells whether to look for a directory or a regular @@ -174,8 +193,23 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { * @return ... */ def lookupPath(path: String, directory: Boolean): AbstractFile = { - val length = path.length() + lookup((f, p, dir) => f.lookupName(p, dir), path, directory) + } + + /** Return an abstract file that does not check that `path' denotes + * an existing file. + */ + def lookupPathUnchecked(path: String, directory: Boolean): AbstractFile = { + lookup((f, p, dir) => f.lookupNameUnchecked(p, dir), path, directory) + } + + private def lookup(getFile: (AbstractFile, String, Boolean) => AbstractFile, + path0: String, + directory: Boolean): AbstractFile = { val separator = File.separatorChar + // trim trailing '/'s + val path = if (path0.charAt(path0.length - 1) == separator) path0.substring(0, path0.length - 1) else path0 + val length = path.length() assert(0 < length && path.lastIndexOf(separator) < length - 1, path) var file = this var start = 0 @@ -183,7 +217,7 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { val index = path.indexOf(separator, start) assert(index < 0 || start < index) val name = path.substring(start, if (index < 0) length else index) - file = file.lookupName(name, if (index < 0) directory else true) + file = getFile(file, name, if (index < 0) directory else true) if ((file eq null) || index < 0) return file start = index + 1 } diff --git a/src/compiler/scala/tools/nsc/io/PlainFile.scala b/src/compiler/scala/tools/nsc/io/PlainFile.scala index 6b11295d49..aa839d5892 100644 --- a/src/compiler/scala/tools/nsc/io/PlainFile.scala +++ b/src/compiler/scala/tools/nsc/io/PlainFile.scala @@ -26,7 +26,7 @@ class PlainFile(val file: File) extends AbstractFile { catch { case _: IOException => file.getAbsolutePath } assert(file ne null) - assert(file.exists(), "non-existent file: " + file) +// assert(file.exists(), "non-existent file: " + file) /** Returns the name of this abstract file. */ def name = file.getName() @@ -34,6 +34,9 @@ class PlainFile(val file: File) extends AbstractFile { /** Returns the path of this abstract file. */ def path = file.getPath() + /** The absolute file. */ + def absolute = new PlainFile(file.getCanonicalFile()) + override def container : AbstractFile = new PlainFile(file.getParentFile) override def input = new FileInputStream(file) @@ -80,4 +83,27 @@ class PlainFile(val file: File) extends AbstractFile { else new PlainFile(child) } + /** Does this abstract file denote an existing file? */ + def create { + if (!exists) + file.createNewFile() + } + + /** Delete the underlying file or directory (recursively). */ + def delete { + if (file.isFile) file.delete + else if (file.isDirectory) { + elements.foreach(_.delete) + file.delete + } + } + + /** 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) + } + } diff --git a/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala b/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala index 403d59d289..53346df53c 100644 --- a/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala +++ b/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala @@ -17,6 +17,9 @@ extends AbstractFile { case None => name case Some(parent) => parent.path+'/'+ name } + + def absolute = this + def container = maybeContainer.get def isDirectory = true var lastModified: Long = System.currentTimeMillis @@ -27,6 +30,22 @@ extends AbstractFile { override def input = error("directories cannot be read") override def output = error("directories cannot be written") + /** 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 + } + + /** 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 val files = mut.Map.empty[String, AbstractFile] // the toList is so that the directory may continue to be diff --git a/src/compiler/scala/tools/nsc/io/VirtualFile.scala b/src/compiler/scala/tools/nsc/io/VirtualFile.scala index 0b9c703757..ba86a165f7 100644 --- a/src/compiler/scala/tools/nsc/io/VirtualFile.scala +++ b/src/compiler/scala/tools/nsc/io/VirtualFile.scala @@ -47,6 +47,8 @@ class VirtualFile(val name: String, _path: String) extends AbstractFile { def path = _path + def absolute = this + /** Returns null. */ final def file: File = null @@ -77,6 +79,16 @@ class VirtualFile(val name: String, _path: String) extends AbstractFile { Iterator.empty } + /** 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 + } + /** * Returns the abstract file in this abstract directory with the * specified name. If there is no such file, returns null. The @@ -92,5 +104,11 @@ class VirtualFile(val name: String, _path: String) extends AbstractFile { null } + /** 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() + //######################################################################## } diff --git a/src/compiler/scala/tools/nsc/io/ZipArchive.scala b/src/compiler/scala/tools/nsc/io/ZipArchive.scala index ab730128bd..c380acd9a3 100644 --- a/src/compiler/scala/tools/nsc/io/ZipArchive.scala +++ b/src/compiler/scala/tools/nsc/io/ZipArchive.scala @@ -101,6 +101,12 @@ final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) root.lookupName(name, directory) } + /** Returns an abstract file with the given name. It does not + * check that it exists. + */ + override def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = + throw new UnsupportedOperationException() + //######################################################################## // Private Methods @@ -231,12 +237,24 @@ final class URLZipArchive(url: URL) extends AbstractFile { def file: File = null + 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() def output = throw new Error("unsupported") @@ -251,6 +269,12 @@ final class URLZipArchive(url: URL) extends AbstractFile { 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] -- cgit v1.2.3