1
1
package ch .epfl .scala .debugadapter .internal
2
2
3
3
import ch .epfl .scala .debugadapter ._
4
+ import ch .epfl .scala .debugadapter .internal .ScalaExtension .*
5
+ import ch .epfl .scala .debugadapter .internal .scalasig .Decompiler
6
+ import ch .epfl .scala .debugadapter .internal .scalasig .ScalaSig
4
7
import org .objectweb .asm ._
5
8
6
9
import java .net .URI
7
10
import java .nio .file ._
8
- import scala .jdk .CollectionConverters .*
9
11
import scala .collection .mutable
10
- import ClassEntryLookUp .readSourceContent
11
-
12
- import scala .util .matching .Regex
13
- import ch .epfl .scala .debugadapter .internal .scalasig .ScalaSig
14
- import ch .epfl .scala .debugadapter .internal .scalasig .Decompiler
15
- import ch .epfl .scala .debugadapter .internal .ScalaExtension .*
12
+ import scala .jdk .CollectionConverters .*
13
+ import scala .util .Properties
16
14
import scala .util .Try
15
+ import scala .util .matching .Regex
17
16
18
- private case class SourceLine ( uri : URI , lineNumber : Int )
17
+ import ClassEntryLookUp . readSourceContent
19
18
20
19
private [internal] case class ClassFile (
21
20
fullyQualifiedName : String ,
@@ -35,35 +34,36 @@ private[internal] case class ClassFile(
35
34
private class ClassEntryLookUp (
36
35
val entry : ClassEntry ,
37
36
fqcnToClassFile : Map [String , ClassFile ],
38
- sourceUriToSourceFile : Map [URI , SourceFile ],
39
- sourceUriToClassFiles : Map [URI , Seq [ClassFile ]],
37
+ sourceUriToSourceFile : Map [SourceFileKey , SourceFile ],
38
+ sourceUriToClassFiles : Map [SourceFileKey , Seq [ClassFile ]],
40
39
classNameToSourceFile : Map [String , SourceFile ],
41
40
missingSourceFileClassFiles : Seq [ClassFile ],
42
41
val orphanClassFiles : Seq [ClassFile ],
43
42
logger : Logger
44
43
) {
45
- private val cachedSourceLines = mutable.Map [SourceLine , Seq [ClassFile ]]()
44
+ private val cachedSourceLines = mutable.Map [SourceLineKey , Seq [ClassFile ]]()
46
45
47
- def sources : Iterable [URI ] = sourceUriToSourceFile.keys
46
+ def sources : Iterable [SourceFileKey ] = sourceUriToSourceFile.keys
48
47
def fullyQualifiedNames : Iterable [String ] = {
49
48
classNameToSourceFile.keys ++
50
49
orphanClassFiles.map(_.fullyQualifiedName) ++
51
50
missingSourceFileClassFiles.map(_.fullyQualifiedName)
52
51
}
53
52
54
53
def getFullyQualifiedClassName (
55
- sourceUri : URI ,
54
+ sourceKey : SourceFileKey ,
56
55
lineNumber : Int
57
56
): Option [String ] = {
58
- val line = SourceLine (sourceUri , lineNumber)
57
+ val line = SourceLineKey (sourceKey , lineNumber)
59
58
60
59
if (! cachedSourceLines.contains(line)) {
61
60
// read and cache line numbers from class files
62
- sourceUriToClassFiles(sourceUri)
61
+ sourceUriToClassFiles
62
+ .getOrElse(sourceKey, Nil )
63
63
.groupBy(_.classSystem)
64
64
.foreach { case (classSystem, classFiles) =>
65
65
classSystem
66
- .within((_, root) => loadLineNumbers(root, classFiles, sourceUri ))
66
+ .within((_, root) => loadLineNumbers(root, classFiles, sourceKey ))
67
67
.warnFailure(logger, s " Cannot load line numbers in ${classSystem.name}" )
68
68
}
69
69
}
@@ -80,7 +80,7 @@ private class ClassEntryLookUp(
80
80
private def loadLineNumbers (
81
81
root : Path ,
82
82
classFiles : Seq [ClassFile ],
83
- sourceUri : URI
83
+ sourceKey : SourceFileKey
84
84
): Unit = {
85
85
for (classFile <- classFiles) {
86
86
val path = root.resolve(classFile.relativePath)
@@ -108,7 +108,7 @@ private class ClassEntryLookUp(
108
108
reader.accept(visitor, 0 )
109
109
110
110
for (n <- lineNumbers) {
111
- val line = SourceLine (sourceUri , n)
111
+ val line = SourceLineKey (sourceKey , n)
112
112
cachedSourceLines.update(
113
113
line,
114
114
cachedSourceLines.getOrElse(line, Seq .empty) :+ classFile
@@ -121,16 +121,16 @@ private class ClassEntryLookUp(
121
121
}
122
122
123
123
def getSourceContent (sourceUri : URI ): Option [String ] =
124
- sourceUriToSourceFile.get(sourceUri).flatMap(readSourceContent(_, logger))
124
+ sourceUriToSourceFile.get(SourceFileKey ( sourceUri) ).flatMap(readSourceContent(_, logger))
125
125
126
- def getSourceFile (fqcn : String ): Option [URI ] =
126
+ def getSourceFileURI (fqcn : String ): Option [URI ] =
127
127
classNameToSourceFile.get(fqcn).map(_.uri)
128
128
129
129
def getSourceContentFromClassName (fqcn : String ): Option [String ] =
130
- getSourceFile (fqcn).flatMap(getSourceContent)
130
+ getSourceFileURI (fqcn).flatMap(getSourceContent)
131
131
132
132
def getClassFiles (sourceUri : URI ): Seq [ClassFile ] =
133
- sourceUriToClassFiles.get(sourceUri).getOrElse(Seq .empty)
133
+ sourceUriToClassFiles.get(SourceFileKey ( sourceUri) ).getOrElse(Seq .empty)
134
134
135
135
def getClassFile (fqcn : String ): Option [ClassFile ] =
136
136
fqcnToClassFile.get(fqcn)
@@ -145,7 +145,7 @@ private class ClassEntryLookUp(
145
145
def fromSource = {
146
146
val scalaSigs =
147
147
for {
148
- sourceFile <- getSourceFile (fqcn).toSeq
148
+ sourceFile <- getSourceFileURI (fqcn).toSeq
149
149
if sourceFile.toString.endsWith(" .scala" )
150
150
classFile <- getClassFiles(sourceFile)
151
151
if fqcn.startsWith(classFile.fullyQualifiedName + " $" )
@@ -182,21 +182,21 @@ private object ClassEntryLookUp {
182
182
classFiles.map(c => (c.fullyQualifiedName, c)).toMap
183
183
184
184
val sourceFileToRoot = sourceLookUps.flatMap(l => l.sourceFiles.map(f => (f -> l.root))).toMap
185
- val sourceUriToSourceFile = sourceLookUps.flatMap(_.sourceFiles).map(f => (f.uri, f)).toMap
185
+ val sourceUriToSourceFile = sourceLookUps.flatMap(_.sourceFiles).map(f => (SourceFileKey ( f.uri) , f)).toMap
186
186
val sourceNameToSourceFile = sourceLookUps.flatMap(_.sourceFiles).groupBy(f => f.fileName)
187
187
188
188
val classNameToSourceFile = mutable.Map [String , SourceFile ]()
189
- val sourceUriToClassFiles = mutable.Map [URI , Seq [ClassFile ]]()
189
+ val sourceUriToClassFiles = mutable.Map [SourceFileKey , Seq [ClassFile ]]()
190
190
val orphanClassFiles = mutable.Buffer [ClassFile ]()
191
191
val missingSourceFileClassFiles = mutable.Buffer [ClassFile ]()
192
192
193
193
for (classFile <- classFiles) {
194
194
def recordSourceFile (sourceFile : SourceFile ): Unit = {
195
195
classNameToSourceFile.put(classFile.fullyQualifiedName, sourceFile)
196
196
sourceUriToClassFiles.update(
197
- sourceFile.uri,
197
+ SourceFileKey ( sourceFile.uri) ,
198
198
sourceUriToClassFiles.getOrElse(
199
- sourceFile.uri,
199
+ SourceFileKey ( sourceFile.uri) ,
200
200
Seq .empty
201
201
) :+ classFile
202
202
)
@@ -358,3 +358,31 @@ private object ClassEntryLookUp {
358
358
}
359
359
}
360
360
}
361
+
362
+ /**
363
+ * On a case-insensitive system we need to sanitize all URIs to use them as Map keys.
364
+ */
365
+ private case class SourceFileKey private (sanitizeUri : URI )
366
+
367
+ private object SourceFileKey {
368
+ private val isCaseSensitiveFileSystem = Properties .isWin || Properties .isMac
369
+
370
+ def apply (uri : URI ): SourceFileKey = {
371
+ val sanitizeUri : URI =
372
+ if (isCaseSensitiveFileSystem) {
373
+ uri.getScheme match {
374
+ case " file" => URI .create(uri.toString.toUpperCase)
375
+ case " jar" | " zip" if uri.toString.contains(" !/" ) =>
376
+ // The contents of jars are case-sensitive no matter what the filesystem is.
377
+ val parts = uri.toString.split(" !/" , 2 ).toSeq
378
+ val head = parts.head.toUpperCase()
379
+ val tail = parts.tail.mkString(" !/" )
380
+ URI .create(s " $head!/ $tail" )
381
+ case _ => uri
382
+ }
383
+ } else uri
384
+ new SourceFileKey (sanitizeUri)
385
+ }
386
+ }
387
+
388
+ private case class SourceLineKey (sourceFile : SourceFileKey , lineNumber : Int )
0 commit comments