package scala.tools.nsc package dependencies; import java.io.{InputStream, OutputStream, PrintStream, InputStreamReader, BufferedReader} import io.{AbstractFile, PlainFile} import scala.collection._; trait Files { self : SubComponent => 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); } } val dependencies = new Tracker val targets = new Tracker; def isEmpty = dependencies.isEmpty && targets.isEmpty def emits(source: AbstractFile, result: AbstractFile) = targets(source) += result; def depends(from: AbstractFile, on: AbstractFile) = dependencies(from) += on; 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: AbstractFile) = targets.contains(f.absolute) def invalidatedFiles(maxDepth : Int) = { val direct = new mutable.HashSet[AbstractFile]; for ((file, products) <- targets) { // This looks a bit odd. It may seem like one should invalidate a file // if *any* of its dependencies are older than it. The forall is there // to deal with the fact that a) Some results might have been orphaned // and b) Some files might not need changing. direct(file) ||= products.forall(d => d.lastModified < file.lastModified) } val indirect = dependentFiles(maxDepth, direct) for ((source, targets) <- targets; if direct(source) || indirect(source)){ targets.foreach(_.delete); targets -= source; } (direct, indirect); } /** Return the set 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; for((target, depends) <- dependencies; if !invalid(target); d <- depends){ newInvalidations(target) ||= invalid(d) } indirect ++= newInvalidations; if(!newInvalidations.isEmpty) go(i - 1); else () } go(depth) indirect --= changed } def writeTo(file: AbstractFile, fromFile : AbstractFile => String) { writeToFile(file)(out => writeTo(new PrintStream(out), fromFile)) } def writeTo(print : PrintStream, fromFile : AbstractFile => String) : Unit = { cleanEmpty(); def emit(tracker : Tracker){ for ((f, ds) <- tracker; d <- ds){ print.println(fromFile(f) + " -> " + fromFile(d)); } } print.println(classpath); print.println(FileDependencies.Separator) emit(dependencies); print.println(FileDependencies.Separator) emit(targets); } } object FileDependencies{ val Separator = "-------"; def readFrom(file: AbstractFile, toFile : String => AbstractFile): Option[FileDependencies] = readFromFile(file) { in => val reader = new BufferedReader(new InputStreamReader(in)) val it = new FileDependencies(reader.readLine) reader.readLine var line : String = null while ({line = reader.readLine; (line != null) && (line != Separator)}){ line.split(" -> ") match { case Array(from, on) => it.depends(toFile(from), toFile(on)); case x => global.inform("Parse error: Unrecognised string " + line); return None } } while ({line = reader.readLine; (line != null) && (line != Separator)}){ line.split(" -> ") match { case Array(source, target) => it.emits(toFile(source), toFile(target)); case x => global.inform("Parse error: Unrecognised string " + line); return None } } Some(it) } } def writeToFile[T](file: AbstractFile)(f: OutputStream => T) : T = { val out = file.output try { f(out) } finally { out.close } } def readFromFile[T](file: AbstractFile)(f: InputStream => T) : T = { val in = file.input try{ f(in) } finally { in.close } } }