forked from scalacenter/bloop
/
ClientClassesObserver.scala
65 lines (55 loc) · 2.37 KB
/
ClientClassesObserver.scala
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
package bloop
import java.io.File
import java.util.concurrent.atomic.AtomicReference
import scala.jdk.CollectionConverters._
import bloop.io.AbsolutePath
import bloop.task.Task
import monix.reactive.Observable
import monix.reactive.subjects.PublishSubject
import sbt.internal.inc.PlainVirtualFileConverter
import xsbti.VirtualFileRef
import xsbti.compile.CompileAnalysis
import xsbti.compile.analysis.Stamp
/**
* Each time a new compile analysis is produced for a given client, it is given to
* the [[ClientClassObserver]] which computes the list of classes that changed or got created.
*
* A client can subscribe to the observer to get notified of classes to update.
* It is used by DAP to hot reload classes in the debuggee process.
*
* @param clientClassesDir the class directory for the client
*/
private[bloop] class ClientClassesObserver(val classesDir: AbsolutePath) {
private val converter = PlainVirtualFileConverter.converter
private val previousAnalysis: AtomicReference[CompileAnalysis] = new AtomicReference()
private val classesSubject: PublishSubject[Seq[String]] = PublishSubject()
def observable: Observable[Seq[String]] = classesSubject
def nextAnalysis(analysis: CompileAnalysis): Task[Unit] = {
val prev = previousAnalysis.getAndSet(analysis)
if (prev != null && classesSubject.size > 0) {
Task {
val previousStamps = prev.readStamps.getAllProductStamps
analysis.readStamps.getAllProductStamps.asScala.iterator.collect {
case (vf, stamp) if isClassFile(vf) && isNewer(stamp, previousStamps.get(vf)) =>
getFullyQualifiedClassName(vf)
}.toSeq
}
.flatMap { classesToUpdate =>
Task.fromFuture(classesSubject.onNext(classesToUpdate)).map(_ => ())
}
} else Task.unit
}
private def isClassFile(vf: VirtualFileRef): Boolean = vf.id.endsWith(".class")
private def isNewer(current: Stamp, previous: Stamp): Boolean =
previous == null || {
val currentHash = current.getHash
val previousHash = previous.getHash
currentHash.isPresent &&
(!previousHash.isPresent || currentHash.get != previousHash.get)
}
private def getFullyQualifiedClassName(vf: VirtualFileRef): String = {
val path = converter.toPath(vf)
val relativePath = classesDir.underlying.relativize(path)
relativePath.toString.replace(File.separator, ".").stripSuffix(".class")
}
}