summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIulian Dragos <jaguarul@gmail.com>2009-05-29 14:16:24 +0000
committerIulian Dragos <jaguarul@gmail.com>2009-05-29 14:16:24 +0000
commit9636749e637088f8ba149b73620535cd1992614a (patch)
tree86882e4a89a8ee60609f36b5dc0d4e4c20f368d5 /src
parent036f2602017bf04dc3a40783a531d488bbd9e12a (diff)
downloadscala-9636749e637088f8ba149b73620535cd1992614a.tar.gz
scala-9636749e637088f8ba149b73620535cd1992614a.tar.bz2
scala-9636749e637088f8ba149b73620535cd1992614a.zip
Refactored the existing dependency tracker and ...
Refactored the existing dependency tracker and added a 'build manager' interface for IDE use.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/BuildManager.scala67
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala23
-rw-r--r--src/compiler/scala/tools/nsc/SimpleBuildManager.scala66
-rw-r--r--src/compiler/scala/tools/nsc/dependencies/Changes.scala64
-rw-r--r--src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala58
-rw-r--r--src/compiler/scala/tools/nsc/dependencies/Files.scala179
-rw-r--r--src/compiler/scala/tools/nsc/dependencies/References.scala34
-rw-r--r--src/compiler/scala/tools/nsc/io/AbstractFile.scala38
-rw-r--r--src/compiler/scala/tools/nsc/io/PlainFile.scala28
-rw-r--r--src/compiler/scala/tools/nsc/io/VirtualDirectory.scala19
-rw-r--r--src/compiler/scala/tools/nsc/io/VirtualFile.scala18
-rw-r--r--src/compiler/scala/tools/nsc/io/ZipArchive.scala24
12 files changed, 478 insertions, 140 deletions
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
* <code>directory</code> 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]