summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/dependencies/Files.scala
blob: 194351a13f114c574664f3328ed27e6f6c43fa64 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package scala.tools.nsc
package dependencies

import java.io.{InputStream, OutputStream, PrintStream, InputStreamReader, BufferedReader}
import io.{AbstractFile, PlainFile, VirtualFile}

import scala.collection._


trait Files { self : SubComponent =>

  class FileDependencies(val classpath: String) {
    import FileDependencies._

    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 (_, value) =>
                               value retain (x => x.exists && (x ne removedFile))}
      dependencies retain ((key, value) => key.exists && !value.isEmpty)
      targets foreach {case (_, value) => value retain (_.exists)}
      targets 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) || (source eq removedFile)) {
        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) || (file eq removedFile)

      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)
      }

      go(depth)

      indirect --= changed
    }

    def writeTo(file: AbstractFile, fromFile: AbstractFile => String): Unit =
      writeToFile(file)(out => writeTo(new PrintStream(out), fromFile))

    def writeTo(print: PrintStream, fromFile: AbstractFile => String): Unit = {
      def emit(tracker: Tracker) =
        for ((f, ds) <- tracker; d <- ds) print.println(fromFile(f) + arrow + fromFile(d))

      cleanEmpty
      print.println(classpath)
      print.println(separator)
      emit(dependencies)
      print.println(separator)
      emit(targets)
    }
  }

  object FileDependencies {
    private val separator:String = "-------"
    private val arrow = " -> "
    private val removedFile = new VirtualFile("removed")

    private def validLine(l: String) = (l != null) && (l != 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)

        def readLines(valid: Boolean)(f: (AbstractFile, AbstractFile) => Unit): Boolean = {
          var continue = valid
          var line: String = null
          while (continue && {line = reader.readLine; validLine(line)}) {
            line.split(arrow) match {
              case Array(from, on) => f(toFile(from), toFile(on))
              case _ =>
                global.inform("Parse error: Unrecognised string " + line)
                continue = false
            }
          }
          continue
        }

        reader.readLine

        val dResult = readLines(true)(
          (_, _) match {
            case (null, _)          => // fromFile is removed, it's ok
            case (fromFile, null)   =>
              // onFile is removed, should recompile fromFile
              it.depends(fromFile, removedFile)
            case (fromFile, onFile) => it.depends(fromFile, onFile)
          })

        readLines(dResult)(
          (_, _) match {
            case (null, null)             =>
              // source and target are all removed, it's ok
            case (null, targetFile)       =>
              // source is removed, should remove relative target later
              it.emits(removedFile, targetFile)
            case (_, null)                =>
              // it may has been cleaned outside, or removed during last phase
            case (sourceFile, targetFile) => it.emits(sourceFile, targetFile)
          })

        Some(it)
      }
  }

  def writeToFile[T](file: AbstractFile)(f: OutputStream => T) : T = {
    val out = file.bufferedOutput
    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
    }
  }
}